| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.beans.addproperty; |
| |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.CompilationUnitTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.util.SourcePositions; |
| import com.sun.source.util.TreePath; |
| import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner; |
| import com.sun.source.util.Trees; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.LinkedList; |
| import java.util.List; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.ElementFilter; |
| import javax.swing.JButton; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.JTextComponent; |
| import javax.swing.text.Position; |
| import javax.swing.text.StyledDocument; |
| import org.netbeans.api.editor.guards.GuardedSection; |
| import org.netbeans.api.editor.guards.GuardedSectionManager; |
| import org.netbeans.api.java.lexer.JavaTokenId; |
| import org.netbeans.api.java.source.CodeStyle; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.JavaSource.Phase; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.java.source.TreeMaker; |
| import org.netbeans.api.java.source.TreeUtilities; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import org.netbeans.api.lexer.Token; |
| import org.netbeans.api.lexer.TokenHierarchy; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.editor.GuardedException; |
| import org.netbeans.modules.editor.indent.api.Reformat; |
| import org.netbeans.spi.editor.codegen.CodeGenerator; |
| import org.openide.DialogDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.filesystems.FileObject; |
| import org.openide.loaders.DataObject; |
| import org.openide.text.NbDocument; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * |
| * @author lahvac |
| */ |
| public class AddPropertyCodeGenerator implements CodeGenerator { |
| |
| private JTextComponent component; |
| private String className; |
| private List<String> existingFields; |
| private String[] pcsName; |
| private String[] vcsName; |
| |
| public AddPropertyCodeGenerator(JTextComponent component, String className, List<String> existingFields, String[] pcsName, String[] vcsName) { |
| this.component = component; |
| this.className = className; |
| this.existingFields = existingFields; |
| this.pcsName = pcsName; |
| this.vcsName = vcsName; |
| } |
| |
| public String getDisplayName() { |
| return NbBundle.getMessage(AddPropertyCodeGenerator.class, "DN_AddProperty"); |
| } |
| |
| public void invoke() { |
| Object o = component.getDocument().getProperty(Document.StreamDescriptionProperty); |
| |
| if (o instanceof DataObject) { |
| DataObject d = (DataObject) o; |
| |
| perform(d.getPrimaryFile(), component); |
| } |
| } |
| |
| public void perform(FileObject file, JTextComponent pane) { |
| JButton ok = new JButton(NbBundle.getMessage(AddPropertyCodeGenerator.class, "LBL_ButtonOK")); |
| CodeStyle cs = CodeStyle.getDefault(pane.getDocument()); |
| if(cs == null) { |
| cs = CodeStyle.getDefault(file); |
| } |
| final AddPropertyPanel addPropertyPanel = new AddPropertyPanel(file, className, cs, existingFields, pcsName, vcsName, ok); |
| String caption = NbBundle.getMessage(AddPropertyCodeGenerator.class, "CAP_AddProperty"); |
| String cancel = NbBundle.getMessage(AddPropertyCodeGenerator.class, "LBL_ButtonCancel"); |
| DialogDescriptor dd = new DialogDescriptor(addPropertyPanel,caption, true, new Object[] {ok,cancel}, ok, DialogDescriptor.DEFAULT_ALIGN, null, null); |
| if (DialogDisplayer.getDefault().notify(dd) == ok) { |
| insertCode2(file, pane, addPropertyPanel.getAddPropertyConfig(), cs); |
| } |
| } |
| |
| /** |
| * work around for {@link #insertCode}. |
| * @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=162853">162853</a> |
| * @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=162630">162630</a> |
| */ |
| static void insertCode2(final FileObject file, final JTextComponent pane, final AddPropertyConfig config, CodeStyle cs) { |
| final Document doc = pane.getDocument(); |
| final Reformat r = Reformat.get(pane.getDocument()); |
| final String code = new AddPropertyGenerator().generate(config, cs); |
| final Position[] bounds = new Position[2]; |
| |
| final int offset[] = new int[2]; |
| |
| try { |
| JavaSource.forFileObject(file).runUserActionTask(new Task<CompilationController>() { |
| @Override |
| public void run(CompilationController parameter) throws Exception { |
| parameter.toPhase(Phase.PARSED); |
| Trees trees = parameter.getTrees(); |
| TreeUtilities treeUtils = parameter.getTreeUtilities(); |
| offset[0] = pane.getCaretPosition(); |
| offset[1] = -1; |
| TreePath path = parameter.getTreeUtilities().pathFor(offset[0]); |
| |
| if (path==null || path.getLeaf().getKind() == Tree.Kind.CLASS) { |
| return; |
| } |
| CompilationUnitTree cut = path.getCompilationUnit(); |
| if(path.getLeaf().getKind() != Tree.Kind.ENUM) { |
| while (path != null && path.getParentPath()!=null && |
| (path.getParentPath().getLeaf().getKind() != Tree.Kind.CLASS && |
| path.getParentPath().getLeaf().getKind() != Tree.Kind.ENUM)) { |
| path = path.getParentPath(); |
| } |
| } |
| |
| int enumconstantEnd = -1; |
| int otherStart = -1; |
| if(path.getLeaf().getKind() == Tree.Kind.ENUM || |
| (path.getParentPath() != null && |
| path.getParentPath().getLeaf().getKind() == Tree.Kind.ENUM)) { |
| TreePath clazzPath = path.getLeaf().getKind() != Tree.Kind.ENUM ? path.getParentPath() : path; |
| ClassTree clazz = (ClassTree) clazzPath.getLeaf(); |
| for (Tree tree : clazz.getMembers()) { |
| TreePath treePath = new TreePath(clazzPath, tree); |
| if(treeUtils.isSynthetic(treePath)) { |
| continue; |
| } |
| Element element; |
| if(tree.getKind() == Tree.Kind.VARIABLE && (element = trees.getElement(treePath)) != null && |
| element.getKind() == ElementKind.ENUM_CONSTANT) { |
| int endPosition = (int) trees.getSourcePositions().getEndPosition(cut, tree); |
| enumconstantEnd = Math.max(enumconstantEnd, endPosition); |
| } else if(otherStart == -1) { |
| otherStart = (int) trees.getSourcePositions().getStartPosition(cut, tree); |
| } |
| } |
| |
| if(enumconstantEnd == -1) { |
| enumconstantEnd = treeUtils.findBodySpan(clazz)[0] + 1; |
| } |
| |
| if(otherStart == -1) { |
| otherStart = (int) trees.getSourcePositions().getEndPosition(cut, clazz); |
| } |
| |
| int semicolon = scanForSemicolon(doc, offset, enumconstantEnd, otherStart); |
| |
| if (semicolon == -1) { |
| offset[1] = enumconstantEnd; |
| if (offset[0] <= enumconstantEnd) { |
| offset[0] = enumconstantEnd + 1; |
| } |
| } else { |
| if (offset[0] <= semicolon) { |
| offset[0] = semicolon + 1; |
| } else if(path.getLeaf().getKind() != Tree.Kind.ENUM) { |
| Tree current = path.getLeaf(); |
| offset[0] = (int) trees.getSourcePositions().getEndPosition(cut, current); |
| } |
| } |
| return; |
| } |
| |
| Tree current = path.getLeaf(); |
| offset[0] = (int) trees.getSourcePositions().getEndPosition(cut, current); |
| } |
| }, true); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| r.lock(); |
| try { |
| NbDocument.runAtomicAsUser((StyledDocument) doc, new Runnable() { |
| public void run() { |
| try { |
| GuardedSectionManager manager = GuardedSectionManager.getInstance((StyledDocument) doc); |
| if (manager != null) { |
| for (GuardedSection guard : manager.getGuardedSections()) { |
| if (guard.contains(doc.createPosition(offset[0]), true)) { |
| offset[0] = guard.getEndPosition().getOffset() + 1; |
| break; |
| } |
| } |
| } |
| |
| doc.insertString(offset[0], code, null); |
| if(offset[1] != -1) { |
| doc.insertString(offset[1], ";", null); |
| } |
| Position start = doc.createPosition(offset[0]); |
| Position end = doc.createPosition(offset[0] + code.length()); |
| // r.reformat(Utilities.getRowStart(pane, start.getOffset()), Utilities.getRowEnd(pane, end.getOffset())); |
| r.reformat(start.getOffset(), end.getOffset()); |
| bounds[0] = start; |
| bounds[1] = end; |
| } catch (GuardedException ex) { |
| //workaround for bug 205193 |
| } catch (BadLocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| }); |
| |
| } catch (BadLocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } finally { |
| r.unlock(); |
| } |
| |
| if (bounds[0] != null) { |
| // code insertion to document passed |
| try { |
| JavaSource.forFileObject(file).runModificationTask(new Task<WorkingCopy>() { |
| public void run(WorkingCopy workingCopy) throws Exception { |
| workingCopy.toPhase(Phase.RESOLVED); |
| |
| Position start = bounds[0]; |
| Position end = bounds[1]; |
| |
| new ImportFQNsHack(workingCopy, start.getOffset(), end.getOffset()).scan(workingCopy.getCompilationUnit(), null); |
| |
| CompilationUnitTree cut = workingCopy.getCompilationUnit(); |
| |
| workingCopy.rewrite(cut, workingCopy.getTreeMaker().CompilationUnit(cut.getPackageAnnotations(), cut.getPackageName(), cut.getImports(), cut.getTypeDecls(), cut.getSourceFile())); |
| } |
| }).commit(); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| } |
| |
| private static int scanForSemicolon(Document doc, int[] offset, int start, int end) throws BadLocationException { |
| TokenHierarchy<Document> th = doc != null ? TokenHierarchy.get(doc) : null; |
| List<TokenSequence<?>> embeddedSequences = th != null ? th.embeddedTokenSequences(offset[0], false) : null; |
| TokenSequence<?> seq = embeddedSequences != null ? embeddedSequences.get(embeddedSequences.size() - 1) : null; |
| |
| if (seq == null) { |
| return offset[0]; |
| } |
| |
| seq.move(start); |
| |
| int semicolon = -1; |
| while(seq.moveNext()) { |
| int tokenOffset = seq.offset(); |
| if(tokenOffset > end) { |
| break; |
| } |
| Token<?> t = seq.token(); |
| if(t != null && t.id() == JavaTokenId.SEMICOLON ) { |
| semicolon = tokenOffset; |
| break; |
| } |
| } |
| return semicolon; |
| } |
| |
| private static final class ImportFQNsHack extends ErrorAwareTreePathScanner<Void, Void> { |
| |
| private WorkingCopy wc; |
| private int start; |
| private int end; |
| |
| public ImportFQNsHack(WorkingCopy wc, int start, int end) { |
| this.wc = wc; |
| this.start = start; |
| this.end = end; |
| } |
| |
| @Override |
| public Void visitMemberSelect(MemberSelectTree node, Void p) { |
| int s = (int) wc.getTrees().getSourcePositions().getStartPosition(wc.getCompilationUnit(), node); |
| int e = (int) wc.getTrees().getSourcePositions().getEndPosition(wc.getCompilationUnit(), node); |
| |
| if (s >= start && e <= end) { |
| Element el = wc.getTrees().getElement(getCurrentPath()); |
| |
| if (el != null && (el.getKind().isClass() || el.getKind().isInterface()) && ((TypeElement) el).asType().getKind() != TypeKind.ERROR) { |
| wc.rewrite(node, wc.getTreeMaker().QualIdent(el)); |
| return null; |
| } |
| } |
| |
| return super.visitMemberSelect(node, p); |
| } |
| |
| @Override |
| public Void visitClass(ClassTree node, Void p) { |
| final SourcePositions sourcePositions = wc.getTrees().getSourcePositions(); |
| final TreeMaker make = wc.getTreeMaker(); |
| List<Tree> members = new LinkedList<Tree>(); |
| ClassTree classTree = node; |
| for (Tree member : node.getMembers()) { |
| int s = (int) sourcePositions.getStartPosition(wc.getCompilationUnit(), member); |
| int e = (int) sourcePositions.getEndPosition(wc.getCompilationUnit(), member); |
| if (s >= start && e <= end) { |
| classTree = make.removeClassMember(classTree, member); |
| members.add(member); |
| } |
| } |
| classTree = GeneratorUtils.insertClassMembers(wc, classTree, members, start); |
| wc.rewrite(node, classTree); |
| return super.visitClass(classTree, p); |
| } |
| } |
| |
| public static final class Factory implements CodeGenerator.Factory { |
| |
| public List<? extends CodeGenerator> create(Lookup context) { |
| JTextComponent component = context.lookup(JTextComponent.class); |
| CompilationController cc = context.lookup(CompilationController.class); |
| TreePath path = context.lookup(TreePath.class); |
| while (path != null && !TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind())) { |
| path = path.getParentPath(); |
| } |
| |
| if (component == null || cc == null || path == null) { |
| return Collections.emptyList(); |
| } |
| |
| //find PropertyChangeSupport, VetoableChangeSupport |
| //list all fields to detect collisions |
| Element e = cc.getTrees().getElement(path); |
| |
| if (e == null || !e.getKind().isClass()) { |
| return Collections.emptyList(); |
| } |
| |
| TypeMirror pcs = resolve(cc, "java.beans.PropertyChangeSupport"); //NOI18N |
| TypeMirror vcs = resolve(cc, "java.beans.VetoableChangeSupport"); //NOI18N |
| |
| if (pcs == null || vcs == null) { |
| return Collections.emptyList(); |
| } |
| |
| List<String> existingFields = new LinkedList<String>(); |
| String[] pcsName = new String[2]; |
| String[] vcsName = new String[2]; |
| |
| for (VariableElement field : ElementFilter.fieldsIn(e.getEnclosedElements())) { |
| existingFields.add(field.getSimpleName().toString()); |
| |
| if (field.asType().equals(pcs)) { |
| int i = field.getModifiers().contains(Modifier.STATIC) ? 1 : 0; |
| |
| pcsName[i] = field.getSimpleName().toString(); |
| } |
| |
| if (field.asType().equals(vcs)) { |
| int i = field.getModifiers().contains(Modifier.STATIC) ? 1 : 0; |
| |
| vcsName[i] = field.getSimpleName().toString(); |
| } |
| } |
| |
| String className = ((TypeElement) e).getQualifiedName().toString(); |
| |
| return Collections.singletonList(new AddPropertyCodeGenerator(component, className, existingFields, pcsName,vcsName)); |
| } |
| |
| private static TypeMirror resolve(CompilationInfo info, String s) { |
| TypeElement te = info.getElements().getTypeElement(s); |
| |
| if (te == null) return null; |
| |
| return te.asType(); |
| } |
| } |
| }; |