001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.corext.refactoring.nls;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014:
015: import org.eclipse.core.runtime.Assert;
016: import org.eclipse.core.runtime.CoreException;
017: import org.eclipse.core.runtime.IPath;
018: import org.eclipse.core.runtime.IProgressMonitor;
019: import org.eclipse.core.runtime.OperationCanceledException;
020: import org.eclipse.core.runtime.SubProgressMonitor;
021:
022: import org.eclipse.core.resources.IFile;
023: import org.eclipse.core.resources.IResource;
024: import org.eclipse.core.resources.ResourcesPlugin;
025:
026: import org.eclipse.ltk.core.refactoring.Change;
027: import org.eclipse.ltk.core.refactoring.Refactoring;
028: import org.eclipse.ltk.core.refactoring.RefactoringStatus;
029: import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
030: import org.eclipse.osgi.util.NLS;
031:
032: import org.eclipse.jdt.core.ICompilationUnit;
033: import org.eclipse.jdt.core.IPackageFragment;
034: import org.eclipse.jdt.core.IType;
035: import org.eclipse.jdt.core.JavaModelException;
036: import org.eclipse.jdt.core.dom.CompilationUnit;
037:
038: import org.eclipse.jdt.internal.corext.SourceRange;
039: import org.eclipse.jdt.internal.corext.refactoring.Checks;
040: import org.eclipse.jdt.internal.corext.refactoring.base.JavaStringStatusContext;
041: import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationStateChange;
042: import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
043: import org.eclipse.jdt.internal.corext.util.Messages;
044:
045: import org.eclipse.jdt.ui.SharedASTProvider;
046:
047: public class NLSRefactoring extends Refactoring {
048:
049: public static final String BUNDLE_NAME = "BUNDLE_NAME"; //$NON-NLS-1$
050: public static final String PROPERTY_FILE_EXT = ".properties"; //$NON-NLS-1$
051: public static final String DEFAULT_ACCESSOR_CLASSNAME = "Messages"; //$NON-NLS-1$
052:
053: public static final String KEY = "${key}"; //$NON-NLS-1$
054: public static final String DEFAULT_SUBST_PATTERN = "getString(" + KEY + ")"; //$NON-NLS-1$ //$NON-NLS-2$
055:
056: public static final String DEFAULT_PROPERTY_FILENAME = "messages"; //$NON-NLS-1$
057:
058: //private IPath fPropertyFilePath;
059:
060: private String fAccessorClassName;
061: private IPackageFragment fAccessorClassPackage;
062: private String fResourceBundleName;
063: private IPackageFragment fResourceBundlePackage;
064:
065: private String fSubstitutionPattern;
066: private ICompilationUnit fCu;
067: private NLSSubstitution[] fSubstitutions;
068:
069: private String fPrefix;
070:
071: /**
072: * <code>true</code> if the standard resource bundle mechanism
073: * is used and <code>false</code> NLSing is done the Eclipse way.
074: */
075: private boolean fIsEclipseNLS;
076:
077: private NLSRefactoring(ICompilationUnit cu) {
078: Assert.isNotNull(cu);
079: fCu = cu;
080:
081: CompilationUnit astRoot = SharedASTProvider.getAST(fCu,
082: SharedASTProvider.WAIT_YES, null);
083: NLSHint nlsHint = new NLSHint(fCu, astRoot);
084:
085: fSubstitutions = nlsHint.getSubstitutions();
086: setAccessorClassName(nlsHint.getAccessorClassName());
087: setAccessorClassPackage(nlsHint.getAccessorClassPackage());
088: setIsEclipseNLS(detectIsEclipseNLS());
089: setResourceBundleName(nlsHint.getResourceBundleName());
090: setResourceBundlePackage(nlsHint.getResourceBundlePackage());
091: setSubstitutionPattern(DEFAULT_SUBST_PATTERN);
092:
093: String cuName = fCu.getElementName();
094: if (fIsEclipseNLS)
095: setPrefix(cuName.substring(0, cuName.length() - 5) + "_"); // A.java -> A_ //$NON-NLS-1$
096: else
097: setPrefix(cuName.substring(0, cuName.length() - 4)); // A.java -> A.
098: }
099:
100: public static NLSRefactoring create(ICompilationUnit cu) {
101: if (cu == null || !cu.exists())
102: return null;
103: return new NLSRefactoring(cu);
104: }
105:
106: /**
107: * no validation is done
108: *
109: * @param pattern
110: * Example: "Messages.getString(${key})". Must not be
111: * <code>null</code>. should (but does not have to) contain
112: * NLSRefactoring.KEY (default value is $key$) only the first
113: * occurrence of this key will be used
114: */
115: public void setSubstitutionPattern(String pattern) {
116: Assert.isNotNull(pattern);
117: fSubstitutionPattern = pattern;
118: }
119:
120: /**
121: * to show the pattern in the UI
122: * @return the substitution pattern
123: */
124: public String getSubstitutionPattern() {
125: if (fIsEclipseNLS)
126: return KEY;
127: else
128: return fSubstitutionPattern;
129: }
130:
131: public ICompilationUnit getCu() {
132: return fCu;
133: }
134:
135: public String getName() {
136: return Messages.format(
137: NLSMessages.NLSRefactoring_compilation_unit, fCu
138: .getElementName());
139: }
140:
141: public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
142: throws CoreException {
143:
144: if (fSubstitutions.length == 0) {
145: String message = Messages.format(
146: NLSMessages.NLSRefactoring_no_strings, fCu
147: .getElementName());
148: return RefactoringStatus.createFatalErrorStatus(message);
149: }
150: return new RefactoringStatus();
151: }
152:
153: public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
154: throws CoreException {
155: checkParameters();
156: try {
157:
158: pm.beginTask(NLSMessages.NLSRefactoring_checking, 5);
159:
160: RefactoringStatus result = new RefactoringStatus();
161:
162: result.merge(checkIfAnythingToDo());
163: if (result.hasFatalError()) {
164: return result;
165: }
166: pm.worked(1);
167:
168: result.merge(validateModifiesFiles());
169: if (result.hasFatalError()) {
170: return result;
171: }
172: pm.worked(1);
173: if (pm.isCanceled())
174: throw new OperationCanceledException();
175:
176: result.merge(checkSubstitutionPattern());
177: pm.worked(1);
178:
179: if (pm.isCanceled())
180: throw new OperationCanceledException();
181:
182: result.merge(checkKeys());
183: pm.worked(1);
184: if (pm.isCanceled())
185: throw new OperationCanceledException();
186:
187: if (!propertyFileExists() && willModifyPropertyFile()) {
188: String msg = Messages.format(
189: NLSMessages.NLSRefactoring_will_be_created,
190: getPropertyFilePath().toString());
191: result.addInfo(msg);
192: }
193: pm.worked(1);
194:
195: return result;
196: } finally {
197: pm.done();
198: }
199: }
200:
201: public Change createChange(IProgressMonitor pm)
202: throws CoreException {
203: try {
204: checkParameters();
205:
206: pm.beginTask("", 3); //$NON-NLS-1$
207:
208: final DynamicValidationStateChange result = new DynamicValidationStateChange(
209: NLSMessages.NLSRefactoring_change_name);
210:
211: boolean createAccessorClass = willCreateAccessorClass();
212: if (NLSSubstitution.countItems(fSubstitutions,
213: NLSSubstitution.EXTERNALIZED) == 0) {
214: createAccessorClass = false;
215: }
216: if (createAccessorClass) {
217: result.add(AccessorClassCreator.create(fCu,
218: fAccessorClassName, getAccessorCUPath(),
219: fAccessorClassPackage, getPropertyFilePath(),
220: fIsEclipseNLS, fSubstitutions,
221: getSubstitutionPattern(),
222: new SubProgressMonitor(pm, 1)));
223: }
224: pm.worked(1);
225:
226: if (willModifySource()) {
227: result.add(NLSSourceModifier.create(getCu(),
228: fSubstitutions, getSubstitutionPattern(),
229: fAccessorClassPackage, fAccessorClassName,
230: fIsEclipseNLS));
231: }
232: pm.worked(1);
233:
234: if (willModifyPropertyFile()) {
235: result.add(NLSPropertyFileModifier.create(
236: fSubstitutions, getPropertyFilePath()));
237: if (isEclipseNLS() && !createAccessorClass) {
238: Change change = AccessorClassModifier.create(
239: getAccessorCu(), fSubstitutions);
240: if (change != null)
241: result.add(change);
242: }
243: }
244: pm.worked(1);
245:
246: return result;
247: } finally {
248: pm.done();
249: }
250: }
251:
252: private void checkParameters() {
253: Assert.isNotNull(fSubstitutions);
254: Assert.isNotNull(fAccessorClassPackage);
255:
256: // these values have defaults ...
257: Assert.isNotNull(fAccessorClassName);
258: Assert.isNotNull(getSubstitutionPattern());
259: }
260:
261: private IFile[] getAllFilesToModify() {
262:
263: List files = new ArrayList(2);
264: if (willModifySource()) {
265: IResource resource = fCu.getResource();
266: if (resource.exists()) {
267: files.add(resource);
268: }
269: }
270:
271: if (willModifyPropertyFile()) {
272: IFile file = getPropertyFileHandle();
273: if (file.exists()) {
274: files.add(file);
275: }
276: }
277:
278: if (willModifyAccessorClass()) {
279: IFile file = getAccessorClassFileHandle();
280: if (file.exists()) {
281: files.add(file);
282: }
283: }
284:
285: return (IFile[]) files.toArray(new IFile[files.size()]);
286: }
287:
288: public IFile getPropertyFileHandle() {
289: return ResourcesPlugin.getWorkspace().getRoot().getFile(
290: getPropertyFilePath());
291: }
292:
293: public IPath getPropertyFilePath() {
294: return fResourceBundlePackage.getPath().append(
295: fResourceBundleName);
296: }
297:
298: public IFile getAccessorClassFileHandle() {
299: return ResourcesPlugin.getWorkspace().getRoot().getFile(
300: getAccessorClassFilePath());
301: }
302:
303: public IPath getAccessorClassFilePath() {
304: return getAccessorCUPath();
305: }
306:
307: private RefactoringStatus validateModifiesFiles() {
308: return Checks.validateModifiesFiles(getAllFilesToModify(),
309: getValidationContext());
310: }
311:
312: //should stop checking if fatal error
313: private RefactoringStatus checkIfAnythingToDo()
314: throws JavaModelException {
315: if (NLSSubstitution.countItems(fSubstitutions,
316: NLSSubstitution.EXTERNALIZED) != 0
317: && willCreateAccessorClass())
318: return null;
319:
320: if (willModifyPropertyFile())
321: return null;
322:
323: if (willModifySource())
324: return null;
325:
326: RefactoringStatus result = new RefactoringStatus();
327: result.addFatalError(NLSMessages.NLSRefactoring_nothing_to_do);
328: return result;
329: }
330:
331: private boolean propertyFileExists() {
332: return getPropertyFileHandle().exists();
333: }
334:
335: private RefactoringStatus checkSubstitutionPattern() {
336: String pattern = getSubstitutionPattern();
337:
338: RefactoringStatus result = new RefactoringStatus();
339: if (pattern.trim().length() == 0) {//
340: result.addError(NLSMessages.NLSRefactoring_pattern_empty);
341: }
342:
343: if (pattern.indexOf(KEY) == -1) {
344: String msg = Messages
345: .format(
346: NLSMessages.NLSRefactoring_pattern_does_not_contain,
347: KEY);
348: result.addWarning(msg);
349: }
350:
351: if (pattern.indexOf(KEY) != pattern.lastIndexOf(KEY)) {
352: String msg = Messages
353: .format(
354: NLSMessages.NLSRefactoring_Only_the_first_occurrence_of,
355: KEY);
356: result.addWarning(msg);
357: }
358:
359: return result;
360: }
361:
362: private RefactoringStatus checkKeys() {
363: RefactoringStatus result = new RefactoringStatus();
364: NLSSubstitution[] subs = fSubstitutions;
365: for (int i = 0; i < subs.length; i++) {
366: NLSSubstitution substitution = subs[i];
367: if ((substitution.getState() == NLSSubstitution.EXTERNALIZED)
368: && substitution.hasStateChanged()) {
369: result.merge(checkKey(substitution.getKey()));
370: }
371: }
372: return result;
373: }
374:
375: private static RefactoringStatus checkKey(String key) {
376: RefactoringStatus result = new RefactoringStatus();
377:
378: if (key == null)
379: result.addFatalError(NLSMessages.NLSRefactoring_null);
380:
381: if (key.startsWith("!") || key.startsWith("#")) { //$NON-NLS-1$ //$NON-NLS-2$
382: RefactoringStatusContext context = new JavaStringStatusContext(
383: key, new SourceRange(0, 0));
384: result.addWarning(NLSMessages.NLSRefactoring_warning,
385: context);
386: }
387:
388: if ("".equals(key.trim())) //$NON-NLS-1$
389: result.addFatalError(NLSMessages.NLSRefactoring_empty);
390:
391: final String[] UNWANTED_STRINGS = {
392: " ", ":", "\"", "\\", "'", "?", "=" }; //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
393: //feature in resource bundle - does not work properly if keys have ":"
394: for (int i = 0; i < UNWANTED_STRINGS.length; i++) {
395: if (key.indexOf(UNWANTED_STRINGS[i]) != -1) {
396: String[] args = { key, UNWANTED_STRINGS[i] };
397: String msg = Messages.format(
398: NLSMessages.NLSRefactoring_should_not_contain,
399: args);
400: result.addError(msg);
401: }
402: }
403: return result;
404: }
405:
406: public boolean willCreateAccessorClass() throws JavaModelException {
407:
408: ICompilationUnit compilationUnit = getAccessorCu();
409: if (compilationUnit.exists()) {
410: return false;
411: }
412:
413: if (typeNameExistsInPackage(fAccessorClassPackage,
414: fAccessorClassName)) {
415: return false;
416: }
417:
418: return (!Checks.resourceExists(getAccessorCUPath()));
419: }
420:
421: private ICompilationUnit getAccessorCu() {
422: return fAccessorClassPackage
423: .getCompilationUnit(getAccessorCUName());
424: }
425:
426: private boolean willModifySource() {
427: NLSSubstitution[] subs = fSubstitutions;
428: for (int i = 0; i < subs.length; i++) {
429: if (subs[i].hasSourceChange())
430: return true;
431: }
432: return false;
433: }
434:
435: private boolean willModifyPropertyFile() {
436: NLSSubstitution[] subs = fSubstitutions;
437: for (int i = 0; i < subs.length; i++) {
438: NLSSubstitution substitution = subs[i];
439: if (substitution.hasPropertyFileChange()) {
440: return true;
441: }
442: }
443: return false;
444: }
445:
446: private boolean willModifyAccessorClass() {
447: if (!isEclipseNLS())
448: return false;
449:
450: NLSSubstitution[] subs = fSubstitutions;
451: for (int i = 0; i < subs.length; i++) {
452: NLSSubstitution substitution = subs[i];
453: if (substitution.hasAccessorClassChange()) {
454: return true;
455: }
456: }
457: return false;
458: }
459:
460: private boolean typeNameExistsInPackage(IPackageFragment pack,
461: String name) throws JavaModelException {
462: return Checks.findTypeInPackage(pack, name) != null;
463: }
464:
465: private String getAccessorCUName() {
466: return getAccessorClassName() + JavaModelUtil.DEFAULT_CU_SUFFIX;
467: }
468:
469: private IPath getAccessorCUPath() {
470: return fAccessorClassPackage.getPath().append(
471: getAccessorCUName());
472: }
473:
474: public NLSSubstitution[] getSubstitutions() {
475: return fSubstitutions;
476: }
477:
478: public String getPrefix() {
479: return fPrefix;
480: }
481:
482: public void setPrefix(String prefix) {
483: fPrefix = prefix;
484: if (fSubstitutions != null) {
485: for (int i = 0; i < fSubstitutions.length; i++)
486: fSubstitutions[i].setPrefix(prefix);
487: }
488: }
489:
490: public void setAccessorClassName(String name) {
491: Assert.isNotNull(name);
492: fAccessorClassName = name;
493: }
494:
495: public void setAccessorClassPackage(IPackageFragment packageFragment) {
496: Assert.isNotNull(packageFragment);
497: fAccessorClassPackage = packageFragment;
498: }
499:
500: /**
501: * Sets whether the Eclipse NLSing mechanism or
502: * standard resource bundle mechanism is used.
503: *
504: * @param isEclipseNLS <code>true</code> if NLSing is done the Eclipse way
505: * and <code>false</code> if the standard resource bundle mechanism is used
506: * @since 3.1
507: */
508: public void setIsEclipseNLS(boolean isEclipseNLS) {
509: fIsEclipseNLS = isEclipseNLS;
510: }
511:
512: public void setResourceBundlePackage(
513: IPackageFragment resourceBundlePackage) {
514: Assert.isNotNull(resourceBundlePackage);
515: fResourceBundlePackage = resourceBundlePackage;
516: }
517:
518: public void setResourceBundleName(String resourceBundleName) {
519: Assert.isNotNull(resourceBundleName);
520: fResourceBundleName = resourceBundleName;
521: }
522:
523: public IPackageFragment getAccessorClassPackage() {
524: return fAccessorClassPackage;
525: }
526:
527: /**
528: * Computes whether the Eclipse NLSing mechanism is used.
529: *
530: * @return <code>true</code> if NLSing is done the Eclipse way
531: * and <code>false</code> if the standard resource bundle mechanism is used
532: * @since 3.1
533: */
534: public boolean detectIsEclipseNLS() {
535: if (getAccessorClassPackage() != null) {
536: ICompilationUnit accessorCU = getAccessorClassPackage()
537: .getCompilationUnit(getAccessorCUName());
538: IType type = accessorCU.getType(getAccessorClassName());
539: if (type.exists()) {
540: try {
541: String super className = type.getSuperclassName();
542: if (!"NLS".equals(super className) && !NLS.class.getName().equals(super className)) //$NON-NLS-1$
543: return false;
544: IType super class = type.newSupertypeHierarchy(null)
545: .getSuperclass(type);
546: return super class != null
547: && NLS.class.getName().equals(
548: super class.getFullyQualifiedName());
549: } catch (JavaModelException e) {
550: return false;
551: }
552: }
553: }
554: return fIsEclipseNLS;
555: }
556:
557: /**
558: * Returns whether the Eclipse NLSing mechanism or
559: * the standard resource bundle mechanism is used.
560: *
561: * @return <code>true</code> if NLSing is done the Eclipse way
562: * and <code>false</code> if the standard resource bundle mechanism is used
563: * @since 3.1
564: */
565: public boolean isEclipseNLS() {
566: return fIsEclipseNLS;
567: }
568:
569: public IPackageFragment getResourceBundlePackage() {
570: return fResourceBundlePackage;
571: }
572:
573: public String getAccessorClassName() {
574: return fAccessorClassName;
575: }
576:
577: public String getResourceBundleName() {
578: return fResourceBundleName;
579: }
580: }
|