| /** |
| * 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.apisupport.hints; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.TypeElement; |
| import org.netbeans.api.java.source.GeneratorUtilities; |
| import org.netbeans.api.java.source.TreeMaker; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import static org.netbeans.modules.apisupport.hints.Bundle.*; |
| import org.netbeans.spi.editor.hints.ChangeInfo; |
| import org.netbeans.spi.editor.hints.Fix; |
| import org.netbeans.spi.editor.hints.Severity; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.loaders.DataObject; |
| import org.openide.util.NbBundle.Messages; |
| import org.openide.util.NbCollections; |
| import org.openide.util.Utilities; |
| import org.openide.util.lookup.ServiceProvider; |
| import com.sun.source.tree.AnnotationTree; |
| import com.sun.source.tree.AssignmentTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.ModifiersTree; |
| import com.sun.source.tree.NewArrayTree; |
| import com.sun.source.tree.Tree; |
| |
| /** |
| * #207219: {@code DataObject.Registration} conversion. |
| * |
| * This Hinter must take care of |
| * |
| * Loader/$mimetype/Factory option 1: dataloader option 2: factory and Loader |
| */ |
| @ServiceProvider(service = Hinter.class) |
| public class DataObjectRegistrationHinter implements Hinter { |
| |
| public static final String ACTIONS_FOLDER = "Actions"; |
| public static final String METHOD_DOPOOL_FACTORY = "method:org.openide.loaders.DataLoaderPool.factory"; |
| private static final String LOADERS_FOLDER = "Loaders/"; |
| private static final String FACTORIES_FOLDER = "Factories"; |
| |
| @Override |
| public void process(final Context ctx) throws Exception { |
| final FileObject file = ctx.file(); |
| // check every file path starting with Loaders |
| if (file.getPath().startsWith(LOADERS_FOLDER)) { |
| if (file.getPath().contains(FACTORIES_FOLDER)) { // its a factory |
| processFactories(ctx, file); |
| } |
| ///// Actions |
| if (file.getPath().endsWith(ACTIONS_FOLDER)) { |
| processActions(ctx, file); |
| } |
| } |
| } |
| |
| // not always of the same mimetype of related dataobject |
| @Messages({ |
| "DataObjectRegistrationHinter.no_DataObject=No visible dataObject, please try to convert Factories folder first", |
| "# {0} - Java source file name", "# {1} - list of MIME types", "DataObjectRegistrationHinter.fix.regular=Convert registration to Java annotation in {0} ({1})", |
| "# {0} - Java source file name", "# {1} - list of MIME types", "DataObjectRegistrationHinter.fix.special=Convert registration to Java annotation in {0} ({1}) <b>be careful mimeType not matching</b>" |
| }) |
| private void processActions(final Context ctx, final FileObject file) throws Exception { |
| |
| if (annotationsActionsDataObjectAvailable(ctx)) { |
| // infer mimetype from path |
| String actionsMime = Utility.getMimeTypeFromActionsPath(file.getPath()); |
| // |
| // try get DataObject / DataObject.Factory available in this project |
| Map<String, List<String>> visibleLoaderFactories = new HashMap<String, List<String>>(); |
| FileObject startingPath = file.getFileSystem().findResource(LOADERS_FOLDER); |
| if (startingPath != null) { |
| for (FileObject aLoadersFileObject : NbCollections.iterable(startingPath.getChildren(true))) { |
| if (aLoadersFileObject.getPath().contains(FACTORIES_FOLDER)) { // its a factory |
| |
| final Object instanceCreate = ctx.instanceAttribute(aLoadersFileObject); |
| if (instanceCreate != null) { |
| |
| if (!METHOD_DOPOOL_FACTORY.equals(instanceCreate)) { |
| |
| String ic = instanceCreate.toString(); |
| |
| if (visibleLoaderFactories.containsKey(ic)) { |
| visibleLoaderFactories.get(ic).add(Utility.getMimeTypeFromFactoryPath(aLoadersFileObject.getPath())); |
| } else { |
| List<String> mime = new LinkedList<String>(); |
| mime.add(Utility.getMimeTypeFromFactoryPath(aLoadersFileObject.getPath())); |
| visibleLoaderFactories.put(ic, mime); |
| } |
| } |
| } |
| } |
| } |
| if (visibleLoaderFactories.isEmpty()) { |
| ctx.addHint(Severity.VERIFIER, DataObjectRegistrationHinter_no_DataObject()); |
| } else { |
| List<Fix> fixes = new ArrayList<Fix>(); // prepare list of fixes |
| boolean mimeMatch = false; |
| for (Map.Entry<String, List<String>> loader : visibleLoaderFactories.entrySet()) { |
| for (String aMime : loader.getValue()) { |
| if (actionsMime.equals(aMime)) { |
| mimeMatch = true; |
| } |
| } |
| } |
| for (Map.Entry<String, List<String>> loader : visibleLoaderFactories.entrySet()) { // |
| boolean restrict = true; |
| // mime list |
| StringBuilder sbMime = new StringBuilder(); |
| for (String aMime : loader.getValue()) { |
| if (actionsMime.equals(aMime)) { |
| restrict = false; |
| } |
| sbMime.append(aMime); |
| |
| sbMime.append(","); |
| } |
| sbMime.deleteCharAt(sbMime.length() - 1); |
| String fname = loader.getKey().substring(loader.getKey().lastIndexOf(".") + 1) + ".java"; |
| final String text = mimeMatch ? DataObjectRegistrationHinter_fix_regular(fname, sbMime) : DataObjectRegistrationHinter_fix_special(fname, sbMime); |
| final String fixParam = loader.getKey(); |
| if (!mimeMatch) { |
| restrict = false; |
| } |
| if (!restrict) { |
| fixes.add(new Fix() { |
| |
| @Override |
| public String getText() { |
| return text; |
| } |
| |
| @Override |
| public ChangeInfo implement() throws Exception { |
| ctx.findAndModifyDeclaration(fixParam, new RegisterActionsDataObject(ctx)); |
| return null; |
| } |
| }); |
| } |
| |
| } |
| if (!fixes.isEmpty()) { |
| ctx.addHint(Severity.WARNING, ctx.standardAnnotationDescription(), fixes.toArray(new Fix[fixes.size()])); |
| } |
| } |
| |
| } |
| } |
| } |
| |
| private Object getInstanceDataObject(Object instanceCreate, FileObject file) { |
| if (METHOD_DOPOOL_FACTORY.equals(instanceCreate)) { |
| return "new:" + file.getAttribute("literal:dataObjectClass"); |
| } else { |
| return instanceCreate; |
| } |
| } |
| |
| private void processFactories(final Context ctx, final FileObject file) throws Exception { |
| final Object instanceCreate = ctx.instanceAttribute(file); |
| if (instanceCreate == null) { |
| return; |
| } |
| if (checkAttributes(file, ctx)) { |
| ctx.addStandardAnnotationHint(new Callable<Void>() { |
| |
| @Override |
| public Void call() throws Exception { |
| if (!annotationsDataObjectAvailable(ctx)) { |
| return null; |
| } |
| ctx.findAndModifyDeclaration(getInstanceDataObject(instanceCreate, file), new RegisterDataObject(ctx)); |
| return null; |
| } |
| }); |
| } |
| } |
| |
| @Messages("DataObjectRegistrationHinter.missing_dep=You must add a dependency on org.openide.loaders (7.36+) before using this fix.") |
| private boolean annotationsDataObjectAvailable(Context ctx) { |
| if (ctx.canAccess(DataObject.Registration.class.getName()) |
| && ctx.canAccess(DataObject.Registrations.class.getName())) { |
| return true; |
| } else { |
| DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(DataObjectRegistrationHinter_missing_dep(), NotifyDescriptor.WARNING_MESSAGE)); |
| return false; |
| } |
| } |
| |
| //@Messages("DataObjectRegistrationHinter.missing_org.openide.awt=You must add a dependency on org.openide.awt (7.27+) before using this fix.") |
| private boolean annotationsActionsDataObjectAvailable(Context ctx) { |
| if (ctx.canAccess("org.openide.awt.ActionReferences")) { |
| return true; |
| } else { |
| DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(ActionRegistrationHinter_missing_org_openide_awt(), NotifyDescriptor.WARNING_MESSAGE)); |
| return false; |
| } |
| } |
| |
| @Messages({ |
| "# {0} - file attribute name", "DataObjectHinter_unrecognized_attr=Unrecognized DataObject attribute: {0}", |
| "DataObjectRegistrationHinter.use.displayName=Please convert to displayName to be able to use Loader hinter" |
| }) |
| private boolean checkAttributes(FileObject file, Context ctx) { |
| boolean attributesCompatible = true; |
| for (String attr : NbCollections.iterable(file.getAttributes())) { |
| if (!attr.matches("mimeType|position|displayName|iconBase|dataObjectClass|instanceCreate|SystemFileSystem.localizingBundle")) { |
| ctx.addHint(Severity.WARNING, DataObjectHinter_unrecognized_attr(attr)); |
| attributesCompatible = false; |
| } |
| } |
| if (file.getAttribute("literal:SystemFileSystem.localizingBundle") != null) { |
| attributesCompatible = false; |
| ctx.addHint(Severity.HINT, DataObjectRegistrationHinter_use_displayName()); |
| } |
| return attributesCompatible; |
| } |
| |
| //Context.ModifyDeclarationTask |
| private static class RegisterDataObject implements Context.ModifyDeclarationTask { |
| |
| private static final String DATAOBJECT_REGISTRATION = "org.openide.loaders.DataObject.Registration"; |
| private static final String DATAOBJECT_REGISTRATIONS = "org.openide.loaders.DataObject.Registrations"; |
| private static final String MIME_TYPE = "mimeType"; |
| private final Context ctx; |
| |
| private RegisterDataObject(Context ctx) { |
| this.ctx = ctx; |
| } |
| |
| @Override |
| public void run(WorkingCopy wc, Element declaration, ModifiersTree modifiers) throws Exception { |
| Map<String, Object> params = new HashMap<String, Object>(); |
| FileObject file = ctx.file(); |
| |
| String displayName = ctx.bundlevalue(file.getAttribute("literal:displayName"), declaration); |
| |
| if (displayName == null) { |
| // checkAttributes method tries to warn user to avoid this fallback |
| // unaware if fix can be chained |
| displayName = "#TODO"; |
| } |
| |
| // parameters of annotation |
| |
| // mimeType is mandatory |
| if (file.getAttribute(MIME_TYPE) != null) { |
| params.put(MIME_TYPE, file.getAttribute(MIME_TYPE)); |
| } else { |
| if (Utility.getMimeTypeFromFactoryPath(file.getPath()).isEmpty()) { |
| throw new IllegalArgumentException("Could not find a non empty mimetype"); |
| } |
| params.put(MIME_TYPE, Utility.getMimeTypeFromFactoryPath(file.getPath())); |
| |
| } |
| // rest of annotation parameter |
| params.put("position", file.getAttribute("position")); |
| params.put("displayName", displayName); |
| params.put("iconBase", file.getAttribute("iconBase")); |
| |
| ModifiersTree mt = ctx.addAnnotation(wc, modifiers, DATAOBJECT_REGISTRATION, DATAOBJECT_REGISTRATIONS, params); |
| wc.rewrite(modifiers, GeneratorUtilities.get(wc).importFQNs(mt)); |
| |
| ctx.delete(file); |
| } |
| } |
| |
| private static class RegisterActionsDataObject implements Context.ModifyDeclarationTask { |
| |
| private final Context ctx; |
| |
| private RegisterActionsDataObject(Context ctx) { |
| this.ctx = ctx; |
| } |
| |
| private boolean isSeparator(FileObject fo) { |
| return (fo != null) && (fo.hasExt("instance") && ("javax.swing.JSeparator".equals(fo.getAttribute("instanceClass")))); |
| } |
| |
| private boolean isSameLayer(FileObject fo) { // thanks to Jesse |
| return Utilities.compareObjects(ctx.file().getAttribute("layers"), fo.getAttribute("layers")); |
| } |
| |
| @Override |
| public void run(WorkingCopy wc, Element declaration, ModifiersTree modifiers) throws Exception { |
| FileObject file = ctx.file(); |
| List<FileObject> toDelete = new ArrayList<FileObject>(); // store fileobject to delete to allow delete at once |
| // precheck classpath |
| TypeElement annActionID = wc.getElements().getTypeElement("org.openide.awt.ActionID"); |
| if (annActionID == null) { |
| throw new IllegalArgumentException("Could not find ActionID in classpath"); |
| } |
| TypeElement annActionRef = wc.getElements().getTypeElement("org.openide.awt.ActionReference"); |
| if (annActionRef == null) { |
| throw new IllegalArgumentException("Could not find ActionReference in classpath"); |
| } |
| TypeElement annActionRefs = wc.getElements().getTypeElement("org.openide.awt.ActionReferences"); |
| if (annActionRefs == null) { |
| throw new IllegalArgumentException("Could not find ActionReferences in classpath"); |
| } |
| |
| |
| if (!file.isData()) { // if data we are not well placed in the filesystem |
| |
| //list the children registred all layer important for dealing with separator |
| List<FileObject> foList = new ArrayList<FileObject>(); |
| for (FileObject achildren : NbCollections.iterable(file.getData(true))) { |
| foList.add(achildren); |
| |
| } |
| foList = FileUtil.getOrder(foList, true); // order them |
| |
| |
| ListIterator<FileObject> iter = foList.listIterator(); // if first element is a separator prepare |
| |
| TreeMaker make = wc.getTreeMaker(); |
| List<AnnotationTree> anns = new ArrayList<AnnotationTree>(); |
| while (iter.hasNext()) { |
| FileObject fo = iter.next(); |
| if (fo.hasExt("shadow") && isSameLayer(fo)) { // iterate only is the layer |
| // ActionID |
| List<ExpressionTree> argumentsActionID = new ArrayList<ExpressionTree>(); |
| // get Original file |
| FileObject originalFile = FileUtil.getConfigFile(fo.getAttribute("originalFile").toString()); |
| // do category and identifier the way action registration hinter do |
| argumentsActionID.add(make.Assignment(make.Identifier("category"), make.Literal(originalFile.getParent().getPath().substring("Actions/".length())))); |
| argumentsActionID.add(make.Assignment(make.Identifier("id"), make.Literal(originalFile.getName().replace('-', '.')))); |
| |
| // ActionReference |
| |
| List<ExpressionTree> arguments = new ArrayList<ExpressionTree>(); |
| |
| arguments.add(make.Assignment(make.Identifier("id"), make.Annotation(make.QualIdent(annActionID), argumentsActionID))); |
| arguments.add(make.Assignment(make.Identifier("path"), make.Literal(fo.getParent().getPath()))); |
| arguments.add(make.Assignment(make.Identifier("position"), make.Literal(fo.getAttribute("position")))); |
| |
| |
| // separator before ? |
| if (iter.hasPrevious()) { |
| iter.previous(); |
| |
| if (iter.hasPrevious()) { |
| FileObject candidateSep = iter.previous(); |
| |
| iter.next(); |
| if ((isSeparator(candidateSep)) |
| && !toDelete.contains(candidateSep)) { |
| arguments.add(make.Assignment(make.Identifier("separatorBefore"), make.Literal(candidateSep.getAttribute("position")))); |
| toDelete.add(candidateSep); // delete separator |
| } |
| |
| } |
| iter.next(); |
| } |
| // separator after ? |
| if (iter.hasNext()) { |
| FileObject candidateSep = iter.next(); |
| iter.previous(); |
| |
| if (isSeparator(candidateSep)) { |
| arguments.add(make.Assignment(make.Identifier("separatorAfter"), make.Literal(candidateSep.getAttribute("position")))); |
| toDelete.add(candidateSep); // delete separator |
| } |
| } |
| |
| |
| anns.add(make.Annotation(make.QualIdent(annActionRef), arguments)); |
| toDelete.add(fo); |
| |
| } |
| // Missing separator waring in comment ? |
| } |
| |
| |
| if (!anns.isEmpty()) { |
| ModifiersTree nue = null; |
| boolean existingActionReference = false; |
| List<? extends AnnotationTree> existanns = modifiers.getAnnotations(); |
| for (int i = 0; i < existanns.size(); i++) { |
| AnnotationTree ann = existanns.get(i); |
| Tree annotationType = ann.getAnnotationType(); |
| if (annotationType.toString().matches("ActionReferences")) { |
| existingActionReference = true; |
| List<? extends ExpressionTree> args = ann.getArguments(); |
| AssignmentTree assign = (AssignmentTree) args.get(0); |
| if (!assign.getVariable().toString().equals("value")) { |
| throw new Exception("expected value=... for @ActionReference"); |
| } |
| ExpressionTree arg = assign.getExpression(); |
| NewArrayTree arr; |
| if (arg.getKind() == Tree.Kind.STRING_LITERAL) { |
| arr = make.NewArray(null, Collections.<ExpressionTree>emptyList(), Collections.singletonList(arg)); |
| } else if (arg.getKind() == Tree.Kind.NEW_ARRAY) { |
| arr = (NewArrayTree) arg; |
| } else { |
| throw new Exception("unknown arg kind " + arg.getKind() + ": " + arg); |
| } |
| for (ExpressionTree line : anns) { |
| arr = make.addNewArrayInitializer(arr, line); |
| } |
| ann = make.Annotation(annotationType, Collections.singletonList(arr)); |
| |
| |
| nue = make.insertModifiersAnnotation(make.removeModifiersAnnotation(modifiers, i), i, ann); |
| } |
| } |
| |
| if (!existingActionReference) { |
| nue = |
| make.addModifiersAnnotation(modifiers, |
| make.Annotation(make.QualIdent(annActionRefs), |
| Collections.singletonList(make.Assignment(make.Identifier("value"), |
| make.NewArray(null, Collections.<ExpressionTree>emptyList(), |
| anns))))); |
| |
| } |
| wc.rewrite(modifiers, |
| GeneratorUtilities.get(wc).importFQNs(nue)); |
| } |
| |
| for (FileObject fo : toDelete) { |
| ctx.delete(fo); |
| } |
| } |
| } |
| } |
| |
| static class Utility { |
| |
| private static String getMime(String path, String right) { |
| String mimeType = path.replace(LOADERS_FOLDER, ""); |
| mimeType = mimeType.substring(0, mimeType.indexOf(right) - 1);// -1 to remove last file separator |
| return mimeType; |
| } |
| |
| private static String getMimeTypeFromFactoryPath(String path) { |
| return getMime(path, FACTORIES_FOLDER); |
| } |
| |
| private static String getMimeTypeFromActionsPath(String path) { |
| return getMime(path, ACTIONS_FOLDER); |
| } |
| } |
| } |