replaced <code></code> with more concise {@code}
[idea/community.git] / java / compiler / forms-compiler / src / com / intellij / uiDesigner / compiler / Utils.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.uiDesigner.compiler;
17
18 import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
19 import com.intellij.uiDesigner.core.GridConstraints;
20 import com.intellij.uiDesigner.lw.*;
21 import org.jdom.Document;
22 import org.jdom.input.SAXBuilder;
23 import org.xml.sax.Attributes;
24 import org.xml.sax.InputSource;
25 import org.xml.sax.SAXException;
26 import org.xml.sax.helpers.DefaultHandler;
27
28 import javax.swing.*;
29 import javax.xml.parsers.SAXParser;
30 import javax.xml.parsers.SAXParserFactory;
31 import java.awt.*;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.StringReader;
35 import java.lang.reflect.Constructor;
36 import java.lang.reflect.Modifier;
37 import java.net.URL;
38 import java.util.HashSet;
39 import java.util.Set;
40
41 /**
42  * @author Anton Katilin
43  * @author Vladimir Kondratyev
44  *         <p/>
45  *         NOTE: the class must be compilable with JDK 1.3, so any methods and filds introduced in 1.4 or later must not be used
46  */
47 public final class Utils {
48   public static final String FORM_NAMESPACE = "http://www.intellij.com/uidesigner/form/";
49   private static final SAXParser SAX_PARSER = createParser();
50
51   private Utils() {
52   }
53
54   private static SAXParser createParser() {
55     try {
56       return SAXParserFactory.newInstance().newSAXParser();
57     }
58     catch (Exception e) {
59       return null;
60     }
61   }
62
63   /**
64    * @param provider if null, no classes loaded and no properties read
65    */
66   public static LwRootContainer getRootContainer(final String formFileContent, final PropertiesProvider provider) throws Exception {
67     if (formFileContent.indexOf(FORM_NAMESPACE) == -1) {
68       throw new AlienFormFileException();
69     }
70
71     final Document document = new SAXBuilder().build(new StringReader(formFileContent), "UTF-8");
72
73     return getRootContainerFromDocument(document, provider);
74   }
75
76   /**
77    * Get root from the url
78    *
79    * @param formFile the document URL
80    * @param provider the provider
81    * @return the root container
82    * @throws Exception if there is a problem with parsing DOM
83    */
84   public static LwRootContainer getRootContainer(final URL formFile, final PropertiesProvider provider) throws Exception {
85     final Document document = new SAXBuilder().build(formFile);
86     return getRootContainerFromDocument(document, provider);
87   }
88
89
90   /**
91    * Get root from the document
92    *
93    * @param document the parsed document
94    * @param provider the provider
95    * @return the root container
96    * @throws Exception if there is a problem with parsing DOM
97    */
98   private static LwRootContainer getRootContainerFromDocument(Document document, PropertiesProvider provider) throws Exception {
99     final LwRootContainer root = new LwRootContainer();
100     root.read(document.getRootElement(), provider);
101     return root;
102   }
103
104   public static LwRootContainer getRootContainer(final InputStream stream, final PropertiesProvider provider) throws Exception {
105     final Document document = new SAXBuilder().build(stream, "UTF-8");
106
107     return getRootContainerFromDocument(document, provider);
108   }
109
110   public synchronized static String getBoundClassName(final String formFileContent) throws Exception {
111     if (formFileContent.indexOf(FORM_NAMESPACE) == -1) {
112       throw new AlienFormFileException();
113     }
114
115     final String[] className = new String[]{null};
116     try {
117       SAX_PARSER.parse(new InputSource(new StringReader(formFileContent)), new DefaultHandler() {
118         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
119           if ("form".equals(qName)) {
120             className[0] = attributes.getValue("", "bind-to-class");
121             throw new SAXException("stop parsing");
122           }
123         }
124       });
125     }
126     catch (Exception e) {
127       // Do nothing.
128     }
129
130     return className[0];
131   }
132
133   /**
134    * Validates that specified class represents {@link javax.swing.JComponent} with
135    * empty constructor.
136    *
137    * @return descriptive human readable error message or {@code null} if
138    *         no errors were detected.
139    */
140     public static String validateJComponentClass(final ClassLoader loader, final String className, final boolean validateConstructor) {
141         if (loader == null) {
142       throw new IllegalArgumentException("loader cannot be null");
143     }
144     if (className == null) {
145       throw new IllegalArgumentException("className cannot be null");
146     }
147
148     // These classes are not visible for passed class loader!
149     if ("com.intellij.uiDesigner.HSpacer".equals(className) || "com.intellij.uiDesigner.VSpacer".equals(className)) {
150       return null;
151     }
152
153         final Class aClass;
154     try {
155             aClass = Class.forName(className, false, loader);
156     }
157     catch (final ClassNotFoundException exc) {
158       return "Class \"" + className + "\"not found";
159     }
160     catch (NoClassDefFoundError exc) {
161       return "Cannot load class " + className + ": " + exc.getMessage();
162     }
163     catch (ExceptionInInitializerError exc) {
164       return "Cannot initialize class " + className + ": " + exc.getMessage();
165     }
166     catch (UnsupportedClassVersionError exc) {
167       return "Unsupported class version error: " + className;
168     }
169
170     if (validateConstructor) {
171             try {
172                 final Constructor constructor = aClass.getConstructor(new Class[0]);
173                 if ((constructor.getModifiers() & Modifier.PUBLIC) == 0) {
174         return "Class \"" + className + "\" does not have default public constructor";
175       }
176     }
177             catch (final Exception exc) {
178                 return "Class \"" + className + "\" does not have default constructor";
179             }
180         }
181
182     // Check that JComponent is accessible via the loader
183
184         if (!JComponent.class.isAssignableFrom(aClass)) {
185         return "Class \"" + className + "\" is not an instance of javax.swing.JComponent";
186       }
187
188     return null;
189   }
190
191   public static void validateNestedFormLoop(final String formName, final NestedFormLoader nestedFormLoader)
192     throws CodeGenerationException, RecursiveFormNestingException {
193     validateNestedFormLoop(formName, nestedFormLoader, null);
194   }
195
196   public static void validateNestedFormLoop(final String formName, final NestedFormLoader nestedFormLoader, final String targetForm)
197     throws CodeGenerationException, RecursiveFormNestingException {
198     HashSet usedFormNames = new HashSet();
199     if (targetForm != null) {
200       usedFormNames.add(targetForm);
201     }
202     validateNestedFormLoop(usedFormNames, formName, nestedFormLoader);
203   }
204
205   private static void validateNestedFormLoop(final Set usedFormNames, final String formName, final NestedFormLoader nestedFormLoader)
206     throws CodeGenerationException, RecursiveFormNestingException {
207     if (usedFormNames.contains(formName)) {
208       throw new RecursiveFormNestingException();
209     }
210     usedFormNames.add(formName);
211     final LwRootContainer rootContainer;
212     try {
213       rootContainer = nestedFormLoader.loadForm(formName);
214     }
215     catch (Exception e) {
216       throw new CodeGenerationException(null, "Error loading nested form: " + e.getMessage(), e);
217     }
218     final Set thisFormNestedForms = new HashSet();
219     final CodeGenerationException[] validateExceptions = new CodeGenerationException[1];
220     final RecursiveFormNestingException[] recursiveNestingExceptions = new RecursiveFormNestingException[1];
221     rootContainer.accept(new ComponentVisitor() {
222       public boolean visit(final IComponent component) {
223         if (component instanceof LwNestedForm) {
224           LwNestedForm nestedForm = (LwNestedForm)component;
225           if (!thisFormNestedForms.contains(nestedForm.getFormFileName())) {
226             thisFormNestedForms.add(nestedForm.getFormFileName());
227             try {
228               validateNestedFormLoop(usedFormNames, nestedForm.getFormFileName(), nestedFormLoader);
229             }
230             catch (RecursiveFormNestingException e) {
231               recursiveNestingExceptions[0] = e;
232               return false;
233             }
234             catch (CodeGenerationException e) {
235               validateExceptions[0] = e;
236               return false;
237             }
238           }
239         }
240         return true;
241       }
242     });
243     if (recursiveNestingExceptions[0] != null) {
244       throw recursiveNestingExceptions[0];
245     }
246     if (validateExceptions[0] != null) {
247       throw validateExceptions[0];
248     }
249   }
250
251   public static String findNotEmptyPanelWithXYLayout(final IComponent component) {
252     if (!(component instanceof IContainer)) {
253       return null;
254     }
255     final IContainer container = (IContainer)component;
256     if (container.getComponentCount() == 0) {
257       return null;
258     }
259     if (container.isXY()) {
260       return container.getId();
261     }
262     for (int i = 0; i < container.getComponentCount(); i++) {
263       String id = findNotEmptyPanelWithXYLayout(container.getComponent(i));
264       if (id != null) {
265         return id;
266       }
267     }
268     return null;
269   }
270
271   public static int getHGap(LayoutManager layout) {
272     if (layout instanceof BorderLayout) {
273       return ((BorderLayout)layout).getHgap();
274     }
275     if (layout instanceof CardLayout) {
276       return ((CardLayout)layout).getHgap();
277     }
278     return 0;
279   }
280
281   public static int getVGap(LayoutManager layout) {
282     if (layout instanceof BorderLayout) {
283       return ((BorderLayout)layout).getVgap();
284     }
285     if (layout instanceof CardLayout) {
286       return ((CardLayout)layout).getVgap();
287     }
288     return 0;
289   }
290
291   public static int getCustomCreateComponentCount(final IContainer container) {
292     final int[] result = new int[1];
293     result[0] = 0;
294     container.accept(new ComponentVisitor() {
295       public boolean visit(IComponent c) {
296         if (c.isCustomCreate()) {
297           result[0]++;
298         }
299         return true;
300       }
301     });
302     return result[0];
303   }
304
305   public static Class suggestReplacementClass(Class componentClass) {
306     while (true) {
307       componentClass = componentClass.getSuperclass();
308       if (componentClass.equals(JComponent.class)) {
309         return JPanel.class;
310       }
311       if ((componentClass.getModifiers() & (Modifier.ABSTRACT | Modifier.PRIVATE)) != 0) {
312         continue;
313       }
314       try {
315         componentClass.getConstructor(new Class[]{});
316       }
317       catch (NoSuchMethodException ex) {
318         continue;
319       }
320       return componentClass;
321     }
322   }
323
324   public static InstrumentationClassFinder.PseudoClass suggestReplacementClass(InstrumentationClassFinder.PseudoClass componentClass) throws ClassNotFoundException, IOException {
325     final InstrumentationClassFinder.PseudoClass jComponentClass = componentClass.getFinder().loadClass(JComponent.class.getName());
326     while (true) {
327       componentClass = componentClass.getSuperClass();
328       if (componentClass.equals(jComponentClass)) {
329         return componentClass.getFinder().loadClass(JPanel.class.getName());
330       }
331       if ((componentClass.getModifiers() & (Modifier.ABSTRACT | Modifier.PRIVATE)) != 0) {
332         continue;
333       }
334       if (!componentClass.hasDefaultPublicConstructor()) {
335         continue;
336       }
337       return componentClass;
338     }
339   }
340
341   public static int alignFromConstraints(final GridConstraints gc, final boolean horizontal) {
342     int anchor = gc.getAnchor();
343     int fill = gc.getFill();
344     int leftMask = horizontal ? GridConstraints.ANCHOR_WEST : GridConstraints.ANCHOR_NORTH;
345     int rightMask = horizontal ? GridConstraints.ANCHOR_EAST : GridConstraints.ANCHOR_SOUTH;
346     int fillMask = horizontal ? GridConstraints.FILL_HORIZONTAL : GridConstraints.FILL_VERTICAL;
347     if ((fill & fillMask) != 0) return GridConstraints.ALIGN_FILL;
348     if ((anchor & rightMask) != 0) return GridConstraints.ALIGN_RIGHT;
349     if ((anchor & leftMask) != 0) return GridConstraints.ALIGN_LEFT;
350     return GridConstraints.ALIGN_CENTER;
351   }
352
353   public static boolean isBoundField(IComponent component, String fieldName) {
354     if (fieldName.equals(component.getBinding())) {
355       return true;
356     }
357     if (component instanceof IContainer) {
358       IContainer container = (IContainer)component;
359       for (int i = 0; i < container.getComponentCount(); i++) {
360         if (isBoundField(container.getComponent(i), fieldName)) {
361           return true;
362         }
363       }
364     }
365     return false;
366   }
367 }