|
1 /* |
|
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package jdk.nashorn.tools.jjs; |
|
27 |
|
28 import java.io.IOException; |
|
29 import java.util.ArrayList; |
|
30 import java.util.Arrays; |
|
31 import java.util.Collections; |
|
32 import java.util.List; |
|
33 import java.util.WeakHashMap; |
|
34 import java.util.regex.Pattern; |
|
35 import java.util.stream.Collectors; |
|
36 import jdk.nashorn.internal.runtime.Context; |
|
37 import jdk.nashorn.internal.runtime.JSType; |
|
38 import jdk.nashorn.internal.runtime.NativeJavaPackage; |
|
39 import jdk.nashorn.internal.runtime.PropertyMap; |
|
40 import jdk.nashorn.internal.runtime.ScriptObject; |
|
41 import jdk.nashorn.internal.runtime.ScriptRuntime; |
|
42 import jdk.nashorn.internal.objects.NativeJava; |
|
43 |
|
44 /* |
|
45 * A helper class to get properties of a given object for source code completion. |
|
46 */ |
|
47 final class PropertiesHelper { |
|
48 // Java package properties helper, may be null |
|
49 private PackagesHelper pkgsHelper; |
|
50 // cached properties list |
|
51 private final WeakHashMap<Object, List<String>> propsCache = new WeakHashMap<>(); |
|
52 |
|
53 /** |
|
54 * Construct a new PropertiesHelper. |
|
55 * |
|
56 * @param context the current nashorn Context |
|
57 */ |
|
58 PropertiesHelper(final Context context) { |
|
59 try { |
|
60 this.pkgsHelper = new PackagesHelper(context); |
|
61 } catch (final IOException exp) { |
|
62 if (Main.DEBUG) { |
|
63 exp.printStackTrace(); |
|
64 } |
|
65 this.pkgsHelper = null; |
|
66 } |
|
67 } |
|
68 |
|
69 void close() throws Exception { |
|
70 propsCache.clear(); |
|
71 pkgsHelper.close(); |
|
72 } |
|
73 |
|
74 /** |
|
75 * returns the list of properties of the given object. |
|
76 * |
|
77 * @param obj object whose property list is returned |
|
78 * @return the list of properties of the given object |
|
79 */ |
|
80 List<String> getProperties(final Object obj) { |
|
81 assert obj != null && obj != ScriptRuntime.UNDEFINED; |
|
82 |
|
83 // wrap JS primitives as objects before gettting properties |
|
84 if (JSType.isPrimitive(obj)) { |
|
85 return getProperties(JSType.toScriptObject(obj)); |
|
86 } |
|
87 |
|
88 // Handle Java package prefix case first. Should do it before checking |
|
89 // for its super class ScriptObject! |
|
90 if (obj instanceof NativeJavaPackage) { |
|
91 if (pkgsHelper != null) { |
|
92 return pkgsHelper.getPackageProperties(((NativeJavaPackage)obj).getName()); |
|
93 } else { |
|
94 return Collections.<String>emptyList(); |
|
95 } |
|
96 } |
|
97 |
|
98 // script object - all inherited and non-enumerable, non-index properties |
|
99 if (obj instanceof ScriptObject) { |
|
100 final ScriptObject sobj = (ScriptObject)obj; |
|
101 final PropertyMap pmap = sobj.getMap(); |
|
102 if (propsCache.containsKey(pmap)) { |
|
103 return propsCache.get(pmap); |
|
104 } |
|
105 final String[] keys = sobj.getAllKeys(); |
|
106 List<String> props = Arrays.asList(keys); |
|
107 props = props.stream() |
|
108 .filter(s -> Character.isJavaIdentifierStart(s.charAt(0))) |
|
109 .collect(Collectors.toList()); |
|
110 Collections.sort(props); |
|
111 // cache properties against the PropertyMap |
|
112 propsCache.put(pmap, props); |
|
113 return props; |
|
114 } |
|
115 |
|
116 // java class case - don't refer to StaticClass directly |
|
117 if (NativeJava.isType(ScriptRuntime.UNDEFINED, obj)) { |
|
118 if (propsCache.containsKey(obj)) { |
|
119 return propsCache.get(obj); |
|
120 } |
|
121 final List<String> props = NativeJava.getProperties(obj); |
|
122 Collections.sort(props); |
|
123 // cache properties against the StaticClass representing the class |
|
124 propsCache.put(obj, props); |
|
125 return props; |
|
126 } |
|
127 |
|
128 // any other Java object |
|
129 final Class<?> clazz = obj.getClass(); |
|
130 if (propsCache.containsKey(clazz)) { |
|
131 return propsCache.get(clazz); |
|
132 } |
|
133 |
|
134 final List<String> props = NativeJava.getProperties(obj); |
|
135 Collections.sort(props); |
|
136 // cache properties against the Class object |
|
137 propsCache.put(clazz, props); |
|
138 return props; |
|
139 } |
|
140 |
|
141 // This method creates a regex Pattern to use to do CamelCase |
|
142 // matching. The pattern is derived from user supplied string |
|
143 // containing one or more upper case characters in it. |
|
144 private static Pattern makeCamelCasePattern(final String str) { |
|
145 assert !str.isEmpty(); |
|
146 |
|
147 final char[] chars = str.toCharArray(); |
|
148 final StringBuilder buf = new StringBuilder(); |
|
149 boolean seenUpperCase = false; |
|
150 |
|
151 // Skip first char for case check. Even if it is upper case, |
|
152 // we do not want to put lower case matching pattern before |
|
153 // the first letter! |
|
154 buf.append(chars[0]); |
|
155 |
|
156 for (int idx = 1; idx < chars.length; idx++) { |
|
157 final char ch = chars[idx]; |
|
158 if (ch >= 'A' && ch <= 'Z') { |
|
159 seenUpperCase = true; |
|
160 buf.append("[^A-Z]*"); |
|
161 } |
|
162 buf.append(ch); |
|
163 } |
|
164 |
|
165 if (seenUpperCase) { |
|
166 // match anything at the end! |
|
167 buf.append(".*"); |
|
168 try { |
|
169 return Pattern.compile(buf.toString()); |
|
170 } catch (Exception exp) { |
|
171 } |
|
172 } |
|
173 |
|
174 return null; |
|
175 } |
|
176 |
|
177 /** |
|
178 * Returns the list of properties of the given object that start with the given prefix. |
|
179 * |
|
180 * @param obj object whose property list is returned |
|
181 * @param prefix property prefix to be matched |
|
182 * @return the list of properties of the given object |
|
183 */ |
|
184 List<String> getProperties(final Object obj, final String prefix) { |
|
185 assert prefix != null && !prefix.isEmpty(); |
|
186 List<String> allProps = getProperties(obj); |
|
187 List<String> props = allProps.stream() |
|
188 .filter(s -> s.startsWith(prefix)) |
|
189 .collect(Collectors.toList()); |
|
190 |
|
191 // If no match, try CamelCase completion.. |
|
192 if (props.isEmpty()) { |
|
193 final Pattern pat = makeCamelCasePattern(prefix); |
|
194 if (pat != null) { |
|
195 return allProps.stream() |
|
196 .filter(s -> pat.matcher(s).matches()) |
|
197 .collect(Collectors.toList()); |
|
198 } |
|
199 } |
|
200 |
|
201 return props; |
|
202 } |
|
203 } |