| /* |
| * 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.maven.model; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.SwingUtilities; |
| import javax.swing.event.DocumentEvent; |
| import javax.swing.event.DocumentListener; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import org.netbeans.editor.BaseDocument; |
| import org.netbeans.editor.GuardedDocument; |
| import org.netbeans.modules.editor.NbEditorUtilities; |
| import org.netbeans.modules.maven.model.pom.Build; |
| import org.netbeans.modules.maven.model.pom.POMModel; |
| import org.netbeans.modules.maven.model.pom.POMModelFactory; |
| import org.netbeans.modules.maven.model.pom.Plugin; |
| import org.netbeans.modules.maven.model.settings.SettingsModel; |
| import org.netbeans.modules.maven.model.settings.SettingsModelFactory; |
| import org.netbeans.modules.xml.xam.ComponentEvent; |
| import org.netbeans.modules.xml.xam.ComponentListener; |
| import org.netbeans.modules.xml.xam.Model; |
| import org.netbeans.modules.xml.xam.ModelSource; |
| import org.netbeans.modules.xml.xam.dom.AbstractDocumentModel; |
| import org.openide.awt.StatusDisplayer; |
| import org.openide.cookies.EditorCookie; |
| import org.openide.cookies.SaveCookie; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileSystem; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.loaders.DataObject; |
| import org.openide.text.Line; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| import org.openide.util.UserQuestionException; |
| import org.openide.util.lookup.AbstractLookup; |
| import org.openide.util.lookup.InstanceContent; |
| import org.openide.util.lookup.Lookups; |
| |
| /** |
| * Utility class to create ModelSource (environment) for the |
| * to be created models. |
| * |
| * copied from xml.retriever and customized. |
| * @author mkleint |
| */ |
| public class Utilities { |
| |
| private Utilities() {} |
| |
| private static final Logger logger = Logger.getLogger(Utilities.class.getName()); |
| |
| /** |
| * |
| * @param file |
| * @param editable |
| * @param skeleton current content of the document |
| * @param mimeType |
| * @return |
| */ |
| public static ModelSource createModelSourceForMissingFile(File file, boolean editable, String skeleton, String mimeType) { |
| try { |
| BaseDocument doc = new BaseDocument(false, mimeType); |
| doc.insertString(0, skeleton, null); |
| InstanceContent ic = new InstanceContent(); |
| Lookup lookup = new AbstractLookup(ic); |
| ic.add(file); |
| ic.add(doc); |
| ModelSource ms = new ModelSource(lookup, editable); |
| return ms; |
| } catch (BadLocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| assert false : "Failed to load the model for non-existing file"; |
| return null; |
| } |
| |
| private static BaseDocument getDocument(final DataObject modelSourceDataObject) throws IOException { |
| if (modelSourceDataObject != null && modelSourceDataObject.isValid()) { |
| EditorCookie ec = modelSourceDataObject.getLookup().lookup(EditorCookie.class); |
| assert ec != null : "Data object "+modelSourceDataObject.getPrimaryFile().getPath()+" has no editor cookies."; |
| Document doc; |
| try { |
| doc = ec.openDocument(); |
| } catch (UserQuestionException uce) { |
| // this exception is thrown if the document is to large |
| // lets just confirm that it is ok |
| uce.confirmed(); |
| doc = ec.openDocument(); |
| } |
| if (doc instanceof BaseDocument) { |
| return (BaseDocument) doc; |
| } else { |
| logger.log(Level.FINER, "Got document of unexpected {0} from {1}", new Object[] {doc.getClass(), modelSourceDataObject}); |
| // Replace with a BaseDocument. Mostly useful for unit test. |
| final BaseDocument doc2 = new GuardedDocument("text/xml"); |
| try { |
| String str = doc.getText(0, doc.getLength()); |
| doc2.insertString(0, str, null); |
| } catch (BadLocationException x) { |
| throw new IOException(x); |
| } |
| final Document orig = doc; |
| doc2.addDocumentListener(new DocumentListener() { |
| public @Override void insertUpdate(DocumentEvent e) { |
| try { |
| orig.insertString(e.getOffset(), doc2.getText(e.getOffset(), e.getLength()), null); |
| } catch (BadLocationException x) { |
| assert false : x; |
| } |
| } |
| public @Override void removeUpdate(DocumentEvent e) { |
| try { |
| orig.remove(e.getOffset(), e.getLength()); |
| } catch (BadLocationException x) { |
| assert false : x; |
| } |
| } |
| public @Override void changedUpdate(DocumentEvent e) {} |
| }); |
| doc2.putProperty(Document.StreamDescriptionProperty, doc.getProperty(Document.StreamDescriptionProperty)); |
| return doc2; |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * This method could be overridden by the Unit testcase to return a special |
| * ModelSource object for a FileObject with custom impl of classes added to the lookup. |
| * This is optional if both getDocument(FO) and createCatalogModel(FO) are overridden. |
| * @param thisFileObj |
| * @return |
| */ |
| public static ModelSource createModelSource(final FileObject thisFileObj) { |
| return createModelSource(thisFileObj, null, null); |
| |
| } |
| |
| public static ModelSource createModelSource(final FileObject thisFileObj, final DataObject dobject, final BaseDocument document) { |
| assert thisFileObj != null : "Null file object."; |
| final File fl = FileUtil.toFile(thisFileObj); |
| boolean editable = (fl != null);// && thisFileObj.canWrite(); |
| |
| Lookup proxyLookup = Lookups.proxy(new Lookup.Provider() { |
| @Override |
| public Lookup getLookup() { |
| List<Object> items = new ArrayList<Object>(); |
| items.add(thisFileObj); |
| try { |
| DataObject dobj = dobject != null ? dobject : DataObject.find(thisFileObj); |
| items.add(dobj); |
| BaseDocument doc = document != null ? document : getDocument(dobj); |
| if (doc != null) { |
| items.add(doc); |
| } else { |
| logger.log(Level.WARNING, "no Document found for {0}", dobj); |
| } |
| } catch (IOException x) { |
| logger.log(Level.SEVERE, x.getMessage()); |
| } |
| if (fl != null) { |
| items.add(fl); |
| } |
| return Lookups.fixed(items.toArray()); |
| } |
| }); |
| return new ModelSource(proxyLookup, editable); |
| } |
| |
| /** |
| * attempts to save the document model to disk. |
| * if model is in transaction, the transaction is ended first, |
| * then dataobject's SaveCookie is called. |
| * |
| * @param model |
| * @throws java.io.IOException if saving fails. |
| */ |
| public static void saveChanges(AbstractDocumentModel<?> model) throws IOException { |
| if (model.isIntransaction()) { |
| // the ISE thrown from endTransction is handled in performPOMModelOperations. |
| model.endTransaction(); |
| } |
| model.sync(); |
| DataObject dobj = model.getModelSource().getLookup().lookup(DataObject.class); |
| if (dobj == null) { |
| final Document doc = model.getModelSource().getLookup().lookup(Document.class); |
| final File file = model.getModelSource().getLookup().lookup(File.class); |
| logger.log(Level.FINE, "saving changes in {0}", file); |
| File parent = file.getParentFile(); |
| FileObject parentFo = FileUtil.toFileObject(parent); |
| if (parentFo == null) { |
| parent.mkdirs(); |
| FileUtil.refreshFor(parent); |
| parentFo = FileUtil.toFileObject(parent); |
| } |
| final FileObject fParentFo = parentFo; |
| if (fParentFo != null) { |
| FileSystem fs = parentFo.getFileSystem(); |
| fs.runAtomicAction(new FileSystem.AtomicAction() { |
| public @Override void run() throws IOException { |
| String text; |
| try { |
| text = doc.getText(0, doc.getLength()); |
| } catch (BadLocationException x) { |
| throw new IOException(x); |
| } |
| FileObject fo = fParentFo.getFileObject(file.getName()); |
| if (fo == null) { |
| fo = fParentFo.createData(file.getName()); |
| } |
| OutputStream os = fo.getOutputStream(); |
| try { |
| os.write(text.getBytes("UTF-8")); |
| } finally { |
| os.close(); |
| } |
| } |
| }); |
| } else { |
| //TODO report |
| } |
| } else { |
| SaveCookie save = dobj.getLookup().lookup(SaveCookie.class); |
| if (save != null) { |
| logger.log(Level.FINE, "saving changes in {0}", dobj); |
| save.save(); |
| } else { |
| logger.log(Level.FINE, "no changes in {0} where modified={1}", new Object[] {dobj, dobj.isModified()}); |
| } |
| } |
| } |
| |
| /** |
| * performs model modifying operations on top of the POM model. After modifications, |
| * the model is persisted to file. |
| * @param pomFileObject |
| * @param operations |
| */ |
| public static void performPOMModelOperations(final FileObject pomFileObject, List<? extends ModelOperation<POMModel>> operations) { |
| assert pomFileObject != null; |
| ModelSource source = Utilities.createModelSource(pomFileObject); |
| performPOMModelOperations(source, operations); |
| } |
| |
| /** |
| * performs model modifying operations on top of the POM model. After modifications, |
| * the model is persisted to file. |
| * @param source |
| * @param operations |
| * @since 1.36 |
| */ |
| public static void performPOMModelOperations(final ModelSource source, List<? extends ModelOperation<POMModel>> operations) { |
| assert source != null; |
| assert operations != null; |
| |
| if (source.getLookup().lookup(BaseDocument.class) == null) { |
| logger.log(Level.WARNING, "#193187: no Document associated with {0}", getPathFromSource(source)); |
| return; |
| } |
| POMModel model = POMModelFactory.getDefault().getModel(source); |
| if (model != null) { |
| try { |
| model.sync(); |
| if (Model.State.VALID != model.getState()) { |
| StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(Utilities.class, "ERR_POM", NbBundle.getMessage(Utilities.class,"ERR_INVALID_MODEL")), StatusDisplayer.IMPORTANCE_ERROR_HIGHLIGHT).clear(10000); |
| return; |
| } |
| if (!model.startTransaction()) { |
| logger.log(Level.WARNING, "Could not start transaction on {0}", getPathFromSource(source)); |
| return; |
| } |
| final AtomicBoolean modified = new AtomicBoolean(); |
| ComponentListener listener = new ComponentListener() { |
| private void change(ComponentEvent evt) { |
| logger.log(Level.FINE, "{0}: {1}", new Object[] {getPathFromSource(source), evt}); |
| modified.set(true); |
| } |
| @Override public void valueChanged(ComponentEvent evt) { |
| change(evt); |
| } |
| @Override public void childrenAdded(ComponentEvent evt) { |
| change(evt); |
| } |
| @Override public void childrenDeleted(ComponentEvent evt) { |
| change(evt); |
| } |
| }; |
| model.addComponentListener(listener); |
| try { |
| for (ModelOperation<POMModel> op : operations) { |
| op.performOperation(model); |
| } |
| model.endTransaction(); |
| } finally { |
| model.removeComponentListener(listener); |
| } |
| if (modified.get()) { |
| Utilities.saveChanges(model); |
| } else { |
| logger.log(Level.FINE, "no changes recorded in {0}", getPathFromSource(source)); |
| } |
| } catch (IOException ex) { |
| StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(Utilities.class, "ERR_POM", ex.getLocalizedMessage()), StatusDisplayer.IMPORTANCE_ERROR_HIGHLIGHT).clear(10000); |
| logger.log(Level.INFO, "Cannot write POM", ex); |
| // Exceptions.printStackTrace(ex); |
| } catch (IllegalStateException ex) { |
| StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(Utilities.class, "ERR_POM", ex.getLocalizedMessage()), StatusDisplayer.IMPORTANCE_ERROR_HIGHLIGHT).clear(10000); |
| logger.log(Level.INFO, "Cannot write POM", ex); |
| } finally { |
| if (model.isIntransaction()) { |
| model.rollbackTransaction(); |
| } |
| } |
| } else { |
| logger.log(Level.WARNING, "Cannot create model from current content of {0}", getPathFromSource(source)); |
| } |
| } |
| |
| private static String getPathFromSource(ModelSource source) { |
| File f = source.getLookup().lookup(File.class); |
| if (f != null) { |
| return f.getAbsolutePath(); |
| } |
| DataObject dob = source.getLookup().lookup(DataObject.class); |
| if (dob != null) { |
| return dob.getPrimaryFile().getPath(); |
| } |
| return source.toString(); |
| } |
| |
| /** |
| * performs model modifying operations on top of the settings.xml model. After modifications, |
| * the model is persisted to file. |
| * @param settingsFileObject |
| * @param operations |
| */ |
| public static void performSettingsModelOperations(FileObject settingsFileObject, List<? extends ModelOperation<SettingsModel>> operations) { |
| assert settingsFileObject != null; |
| assert operations != null; |
| ModelSource source = Utilities.createModelSource(settingsFileObject); |
| SettingsModel model = SettingsModelFactory.getDefault().getModel(source); |
| if (model != null) { |
| try { |
| model.sync(); |
| if (Model.State.VALID != model.getState()) { |
| StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(Utilities.class, "ERR_SETTINGS", NbBundle.getMessage(Utilities.class,"ERR_INVALID_MODEL")), StatusDisplayer.IMPORTANCE_ERROR_HIGHLIGHT).clear(10000); |
| return; |
| } |
| if (!model.startTransaction()) { |
| logger.log(Level.WARNING, "Could not start transaction on {0}", settingsFileObject); |
| return; |
| } |
| for (ModelOperation<SettingsModel> op : operations) { |
| op.performOperation(model); |
| } |
| model.endTransaction(); |
| Utilities.saveChanges(model); |
| } catch (IOException ex) { |
| StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(Utilities.class, "ERR_SETTINGS", ex.getLocalizedMessage()), StatusDisplayer.IMPORTANCE_ERROR_HIGHLIGHT).clear(10000); |
| Logger.getLogger(Utilities.class.getName()).log(Level.INFO, "Cannot write settings.xml", ex); |
| } catch (IllegalStateException ex) { |
| StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(Utilities.class, "ERR_SETTINGS", ex.getLocalizedMessage()), StatusDisplayer.IMPORTANCE_ERROR_HIGHLIGHT).clear(10000); |
| Logger.getLogger(Utilities.class.getName()).log(Level.INFO, "Cannot write settings.xml", ex); |
| } finally { |
| if (model.isIntransaction()) { |
| model.rollbackTransaction(); |
| } |
| } |
| } else { |
| //TODO report error.. what is the error? |
| } |
| } |
| |
| /** |
| * Opens pom at given offset. |
| * @param model the model to open |
| * @param offset position to open at |
| * @since 1.44 |
| */ |
| public static void openAtPosition(final POMModel model, final int offset) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Line line = NbEditorUtilities.getLine(model.getBaseDocument(), offset, false); |
| line.show(Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS); |
| } catch (IndexOutOfBoundsException e) { |
| logger.log(Level.FINE, "document changed", e); |
| } |
| } |
| }); |
| } |
| |
| } |