1 /*******************************************************************************
2  * Copyright (c) 2000, 2011 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.internal.corext.refactoring.nls;
15 
16 import java.io.ByteArrayInputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Properties;
24 
25 import org.eclipse.core.runtime.CoreException;
26 
27 import org.eclipse.core.resources.IFile;
28 import org.eclipse.core.resources.IStorage;
29 
30 import org.eclipse.core.filebuffers.FileBuffers;
31 import org.eclipse.core.filebuffers.ITextFileBuffer;
32 import org.eclipse.core.filebuffers.ITextFileBufferManager;
33 import org.eclipse.core.filebuffers.LocationKind;
34 
35 import org.eclipse.jface.text.IDocument;
36 import org.eclipse.jface.text.IRegion;
37 import org.eclipse.jface.text.Region;
38 
39 import org.eclipse.jdt.core.ICompilationUnit;
40 import org.eclipse.jdt.core.IJavaElement;
41 import org.eclipse.jdt.core.IJavaProject;
42 import org.eclipse.jdt.core.IPackageFragment;
43 import org.eclipse.jdt.core.IPackageFragmentRoot;
44 import org.eclipse.jdt.core.IType;
45 import org.eclipse.jdt.core.ITypeRoot;
46 import org.eclipse.jdt.core.JavaModelException;
47 import org.eclipse.jdt.core.Signature;
48 import org.eclipse.jdt.core.dom.ASTNode;
49 import org.eclipse.jdt.core.dom.ASTVisitor;
50 import org.eclipse.jdt.core.dom.Assignment;
51 import org.eclipse.jdt.core.dom.CompilationUnit;
52 import org.eclipse.jdt.core.dom.Expression;
53 import org.eclipse.jdt.core.dom.IBinding;
54 import org.eclipse.jdt.core.dom.IMethodBinding;
55 import org.eclipse.jdt.core.dom.ITypeBinding;
56 import org.eclipse.jdt.core.dom.IVariableBinding;
57 import org.eclipse.jdt.core.dom.MethodInvocation;
58 import org.eclipse.jdt.core.dom.Modifier;
59 import org.eclipse.jdt.core.dom.Name;
60 import org.eclipse.jdt.core.dom.NodeFinder;
61 import org.eclipse.jdt.core.dom.QualifiedName;
62 import org.eclipse.jdt.core.dom.SimpleName;
63 import org.eclipse.jdt.core.dom.SimpleType;
64 import org.eclipse.jdt.core.dom.StringLiteral;
65 import org.eclipse.jdt.core.dom.TypeLiteral;
66 import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
67 import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
68 
69 import org.eclipse.jdt.internal.corext.dom.Bindings;
70 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
71 
72 import org.eclipse.jdt.internal.ui.JavaPlugin;
73 
74 public class NLSHintHelper {
75 
NLSHintHelper()76 	private NLSHintHelper() {
77 	}
78 
79 	/**
80 	 * Returns the accessor binding info or <code>null</code> if this element is not a nls'ed entry
81 	 *
82 	 * @param astRoot the ast root
83 	 * @param nlsElement the nls element
84 	 * @return the accessor class reference or <code>null</code> if this element is not a nls'ed entry
85 	 */
getAccessorClassReference(CompilationUnit astRoot, NLSElement nlsElement)86 	public static AccessorClassReference getAccessorClassReference(CompilationUnit astRoot, NLSElement nlsElement) {
87 		IRegion region= nlsElement.getPosition();
88 		return getAccessorClassReference(astRoot, region);
89 	}
90 
91 	/**
92 	 * Returns the accessor binding info or <code>null</code> if this element is not a nls'ed entry
93 	 *
94 	 * @param astRoot the ast root
95 	 * @param region the text region
96 	 * @return the accessor class reference or <code>null</code> if this element is not a nls'ed entry
97 	 */
getAccessorClassReference(CompilationUnit astRoot, IRegion region)98 	public static AccessorClassReference getAccessorClassReference(CompilationUnit astRoot, IRegion region) {
99 		return getAccessorClassReference(astRoot, region, false);
100 	}
101 
102 	/**
103 	 * Returns the accessor binding info or <code>null</code> if this element is not a nls'ed entry
104 	 *
105 	 * @param astRoot the ast root
106 	 * @param region the text region
107 	 * @param usedFullyQualifiedName boolean flag to indicate that fully qualified name is used to
108 	 *            refer a NLS key string constant
109 	 * @return the accessor class reference or <code>null</code> if this element is not a nls'ed
110 	 *         entry
111 	 */
getAccessorClassReference(CompilationUnit astRoot, IRegion region, boolean usedFullyQualifiedName)112 	public static AccessorClassReference getAccessorClassReference(CompilationUnit astRoot, IRegion region, boolean usedFullyQualifiedName) {
113 		ASTNode nlsStringLiteral= NodeFinder.perform(astRoot, region.getOffset(), region.getLength());
114 		if (nlsStringLiteral == null)
115 			return null; // not found
116 
117 		ASTNode parent= nlsStringLiteral.getParent();
118 		if (usedFullyQualifiedName) {
119 			parent= parent.getParent();
120 		}
121 
122 		ITypeBinding accessorBinding= null;
123 
124 		if (!usedFullyQualifiedName && nlsStringLiteral instanceof SimpleName && nlsStringLiteral.getLocationInParent() == QualifiedName.NAME_PROPERTY) {
125 			SimpleName name= (SimpleName)nlsStringLiteral;
126 
127 			IBinding binding= name.resolveBinding();
128 			if (binding instanceof IVariableBinding) {
129 				IVariableBinding variableBinding= (IVariableBinding)binding;
130 				if (Modifier.isStatic(variableBinding.getModifiers()))
131 					accessorBinding= variableBinding.getDeclaringClass();
132 			}
133 		}
134 
135 		if (accessorBinding == null) {
136 
137 			if (parent instanceof MethodInvocation) {
138 				MethodInvocation methodInvocation= (MethodInvocation) parent;
139 				List<Expression> args= methodInvocation.arguments();
140 				if (args.size() != 1 && args.indexOf(nlsStringLiteral) != 0) {
141 					return null; // must be the only argument in lookup method
142 				}
143 
144 				Expression firstArgument= args.get(0);
145 				ITypeBinding argumentBinding= firstArgument.resolveTypeBinding();
146 				if (argumentBinding == null || !argumentBinding.getQualifiedName().equals("java.lang.String")) { //$NON-NLS-1$
147 					return null;
148 				}
149 
150 				ITypeBinding typeBinding= methodInvocation.resolveTypeBinding();
151 				if (typeBinding == null || !typeBinding.getQualifiedName().equals("java.lang.String")) { //$NON-NLS-1$
152 					return null;
153 				}
154 
155 				IMethodBinding methodBinding= methodInvocation.resolveMethodBinding();
156 				if (methodBinding == null || !Modifier.isStatic(methodBinding.getModifiers())) {
157 					return null; // only static methods qualify
158 				}
159 
160 				accessorBinding= methodBinding.getDeclaringClass();
161 			} else if (parent instanceof VariableDeclarationFragment) {
162 				VariableDeclarationFragment decl= (VariableDeclarationFragment)parent;
163 				if (decl.getInitializer() != null)
164 					return null;
165 
166 				IBinding binding= decl.resolveBinding();
167 				if (!(binding instanceof IVariableBinding))
168 					return null;
169 
170 				IVariableBinding variableBinding= (IVariableBinding)binding;
171 				if (!Modifier.isStatic(variableBinding.getModifiers()))
172 					return null;
173 
174 				accessorBinding= variableBinding.getDeclaringClass();
175 			}
176 		}
177 
178 		if (accessorBinding == null)
179 			return null;
180 
181 		String resourceBundleName;
182 		resourceBundleName= getResourceBundleName(accessorBinding);
183 
184 		if (resourceBundleName != null)
185 			return new AccessorClassReference(accessorBinding, resourceBundleName, new Region(parent.getStartPosition(), parent.getLength()));
186 
187 		return null;
188 	}
189 
getPackageOfAccessorClass(IJavaProject javaProject, ITypeBinding accessorBinding)190 	public static IPackageFragment getPackageOfAccessorClass(IJavaProject javaProject, ITypeBinding accessorBinding) throws JavaModelException {
191 		if (accessorBinding != null) {
192 			ICompilationUnit unit= Bindings.findCompilationUnit(accessorBinding, javaProject);
193 			if (unit != null) {
194 				return (IPackageFragment) unit.getParent();
195 			}
196 		}
197 		return null;
198 	}
199 
getResourceBundleName(ITypeBinding accessorClassBinding)200 	public static String getResourceBundleName(ITypeBinding accessorClassBinding) {
201 		IJavaElement je= accessorClassBinding.getJavaElement();
202 		if (!(je instanceof IType))
203 			return null;
204 		ITypeRoot typeRoot= ((IType) je).getTypeRoot();
205 		CompilationUnit astRoot= SharedASTProviderCore.getAST(typeRoot, SharedASTProviderCore.WAIT_YES, null);
206 
207 		return getResourceBundleName(astRoot);
208 	}
209 
getResourceBundleName(ITypeRoot input)210 	public static String getResourceBundleName(ITypeRoot input) {
211 		return getResourceBundleName(SharedASTProviderCore.getAST(input, SharedASTProviderCore.WAIT_YES, null));
212 	}
213 
getResourceBundleName(CompilationUnit astRoot)214 	public static String getResourceBundleName(CompilationUnit astRoot) {
215 
216 		if (astRoot == null)
217 			return null;
218 
219 		final Map<Object, Object> resultCollector= new HashMap<>(5);
220 		final Object RESULT_KEY= new Object();
221 		final Object FIELD_KEY= new Object();
222 
223 		astRoot.accept(new ASTVisitor() {
224 
225 			@Override
226 			public boolean visit(MethodInvocation node) {
227 				IMethodBinding method= node.resolveMethodBinding();
228 				if (method == null)
229 					return true;
230 
231 				String name= method.getDeclaringClass().getQualifiedName();
232 				if ((!"java.util.ResourceBundle".equals(name) || !"getBundle".equals(method.getName()) || (node.arguments().size() <= 0)) && //old school //$NON-NLS-1$ //$NON-NLS-2$
233 						(!"org.eclipse.osgi.util.NLS".equals(name) || !"initializeMessages".equals(method.getName()) || (node.arguments().size() != 2))) //Eclipse style //$NON-NLS-1$ //$NON-NLS-2$
234 					return true;
235 
236 				Expression argument= (Expression)node.arguments().get(0);
237 				String bundleName= getBundleName(argument);
238 				if (bundleName != null)
239 					resultCollector.put(RESULT_KEY, bundleName);
240 
241 				if (argument instanceof Name) {
242 					Object fieldNameBinding= ((Name)argument).resolveBinding();
243 					if (fieldNameBinding != null)
244 						resultCollector.put(FIELD_KEY, fieldNameBinding);
245 				}
246 
247 				return false;
248 			}
249 
250 			@Override
251 			public boolean visit(VariableDeclarationFragment node) {
252 				Expression initializer= node.getInitializer();
253 				String bundleName= getBundleName(initializer);
254 				if (bundleName != null) {
255 					Object fieldNameBinding= node.getName().resolveBinding();
256 					if (fieldNameBinding != null)
257 						resultCollector.put(fieldNameBinding, bundleName);
258 					return false;
259 				}
260 				return true;
261 			}
262 
263 			@Override
264 			public boolean visit(Assignment node) {
265 				if (node.getLeftHandSide() instanceof Name) {
266 					String bundleName= getBundleName(node.getRightHandSide());
267 					if (bundleName != null) {
268 						Object fieldNameBinding= ((Name)node.getLeftHandSide()).resolveBinding();
269 						if (fieldNameBinding != null) {
270 							resultCollector.put(fieldNameBinding, bundleName);
271 							return false;
272 						}
273 					}
274 				}
275 				return true;
276 			}
277 
278 			private String getBundleName(Expression initializer) {
279 				if (initializer instanceof StringLiteral)
280 					return ((StringLiteral)initializer).getLiteralValue();
281 
282 				if (initializer instanceof MethodInvocation) {
283 					MethodInvocation methInvocation= (MethodInvocation)initializer;
284 					Expression exp= methInvocation.getExpression();
285 					if ((exp != null) && (exp instanceof TypeLiteral)) {
286 						SimpleType simple= (SimpleType)((TypeLiteral) exp).getType();
287 						ITypeBinding typeBinding= simple.resolveBinding();
288 						if (typeBinding != null)
289 							return typeBinding.getQualifiedName();
290 					}
291 				}
292 				return null;
293 			}
294 
295 		});
296 
297 
298 		Object fieldName;
299 		String result;
300 
301 		result= (String)resultCollector.get(RESULT_KEY);
302 		if (result != null)
303 			return result;
304 
305 		fieldName= resultCollector.get(FIELD_KEY);
306 		if (fieldName != null)
307 			return (String)resultCollector.get(fieldName);
308 
309 		// Now try hard-coded bundle name String field names from NLS tooling:
310 		Iterator<Object> iter= resultCollector.keySet().iterator();
311 		while (iter.hasNext()) {
312 			Object o= iter.next();
313 			if (!(o instanceof IBinding))
314 				continue;
315 			IBinding binding= (IBinding)o;
316 			fieldName= binding.getName();
317 			if (fieldName.equals("BUNDLE_NAME") || fieldName.equals("RESOURCE_BUNDLE") || fieldName.equals("bundleName")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
318 				result= (String)resultCollector.get(binding);
319 				if (result != null)
320 					return result;
321 			}
322 		}
323 
324 		result= (String)resultCollector.get(RESULT_KEY);
325 		if (result != null)
326 			return result;
327 
328 		fieldName= resultCollector.get(FIELD_KEY);
329 		if (fieldName != null)
330 			return (String)resultCollector.get(fieldName);
331 
332 		return null;
333 	}
334 
getResourceBundlePackage(IJavaProject javaProject, String packageName, String resourceName)335 	public static IPackageFragment getResourceBundlePackage(IJavaProject javaProject, String packageName, String resourceName) throws JavaModelException {
336 		for (IPackageFragmentRoot root : javaProject.getAllPackageFragmentRoots()) {
337 			if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
338 				IPackageFragment packageFragment= root.getPackageFragment(packageName);
339 				if (packageFragment.exists()) {
340 					for (Object object : packageFragment.isDefaultPackage() ? root.getNonJavaResources() : packageFragment.getNonJavaResources()) {
341 						if (object instanceof IFile) {
342 							IFile file= (IFile) object;
343 							if (file.getName().equals(resourceName)) {
344 								return packageFragment;
345 							}
346 						}
347 					}
348 				}
349 			}
350 		}
351 		return null;
352 	}
353 
getResourceBundle(ICompilationUnit compilationUnit)354 	public static IStorage getResourceBundle(ICompilationUnit compilationUnit) throws JavaModelException {
355 		IJavaProject project= compilationUnit.getJavaProject();
356 		if (project == null)
357 			return null;
358 
359 		String name= getResourceBundleName(compilationUnit);
360 		if (name == null)
361 			return null;
362 
363 		String packName= Signature.getQualifier(name);
364 		String resourceName= Signature.getSimpleName(name) + NLSRefactoring.PROPERTY_FILE_EXT;
365 
366 		return getResourceBundle(project, packName, resourceName);
367 	}
368 
getResourceBundle(IJavaProject javaProject, String packageName, String resourceName)369 	public static IStorage getResourceBundle(IJavaProject javaProject, String packageName, String resourceName) throws JavaModelException {
370 		for (IPackageFragmentRoot root : javaProject.getAllPackageFragmentRoots()) {
371 			if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
372 				IStorage storage= getResourceBundle(root, packageName, resourceName);
373 				if (storage != null)
374 					return storage;
375 			}
376 		}
377 		return null;
378 	}
379 
getResourceBundle(IPackageFragmentRoot root, String packageName, String resourceName)380 	public static IStorage getResourceBundle(IPackageFragmentRoot root, String packageName, String resourceName) throws JavaModelException {
381 		IPackageFragment packageFragment= root.getPackageFragment(packageName);
382 		if (packageFragment.exists()) {
383 			for (Object object : packageFragment.isDefaultPackage() ? root.getNonJavaResources() : packageFragment.getNonJavaResources()) {
384 				if (JavaModelUtil.isOpenableStorage(object)) {
385 					IStorage storage= (IStorage)object;
386 					if (storage.getName().equals(resourceName)) {
387 						return storage;
388 					}
389 				}
390 			}
391 		}
392 		return null;
393 	}
394 
getResourceBundle(IJavaProject javaProject, AccessorClassReference accessorClassReference)395 	public static IStorage getResourceBundle(IJavaProject javaProject, AccessorClassReference accessorClassReference) throws JavaModelException {
396 		String resourceBundle= accessorClassReference.getResourceBundleName();
397 		if (resourceBundle == null)
398 			return null;
399 
400 		String resourceName= Signature.getSimpleName(resourceBundle) + NLSRefactoring.PROPERTY_FILE_EXT;
401 		String packName= Signature.getQualifier(resourceBundle);
402 		ITypeBinding accessorClass= accessorClassReference.getBinding();
403 
404 		if (accessorClass.isFromSource())
405 			return getResourceBundle(javaProject, packName, resourceName);
406 		else if (accessorClass.getJavaElement() != null)
407 			return getResourceBundle((IPackageFragmentRoot)accessorClass.getJavaElement().getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT), packName, resourceName);
408 
409 		return null;
410 	}
411 
412 	/**
413 	 * Reads the properties from the given storage and
414 	 * returns it.
415 	 *
416 	 * @param javaProject the Java project
417 	 * @param accessorClassReference the accessor class reference
418 	 * @return the properties or <code>null</code> if it was not successfully read
419 	 */
getProperties(IJavaProject javaProject, AccessorClassReference accessorClassReference)420 	public static Properties getProperties(IJavaProject javaProject, AccessorClassReference accessorClassReference) {
421 		try {
422 			IStorage storage= NLSHintHelper.getResourceBundle(javaProject, accessorClassReference);
423 			return getProperties(storage);
424 		} catch (JavaModelException ex) {
425 			// sorry no properties
426 			return null;
427 		}
428 	}
429 
430 	/**
431 	 * Reads the properties from the given storage and
432 	 * returns it.
433 	 *
434 	 * @param storage the storage
435 	 * @return the properties or <code>null</code> if it was not successfully read
436 	 */
getProperties(IStorage storage)437 	public static Properties getProperties(IStorage storage) {
438 		if (storage == null)
439 			return null;
440 
441 		Properties props= new Properties();
442 		InputStream is= null;
443 
444 		ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
445 		try {
446 			if (manager != null) {
447 				ITextFileBuffer buffer= manager.getTextFileBuffer(storage.getFullPath(), LocationKind.NORMALIZE);
448 				if (buffer != null) {
449 					IDocument document= buffer.getDocument();
450 					is= new ByteArrayInputStream(document.get().getBytes());
451 				}
452 			}
453 
454 			// Fallback: read from storage
455 			if (is == null)
456 				is= storage.getContents();
457 
458 			props.load(is);
459 
460 		} catch (IOException e) {
461 			// sorry no properties
462 			return null;
463 		} catch (CoreException e) {
464 			// sorry no properties
465 			return null;
466 		} finally {
467 			if (is != null) try {
468 				is.close();
469 			} catch (IOException e) {
470 				// return properties anyway but log
471 				JavaPlugin.log(e);
472 			}
473 		}
474 		return props;
475 	}
476 
477 }
478