/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed 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.napile.idea.thermit.config.impl;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.napile.idea.thermit.config.AntBuildFileBase;
import org.napile.idea.thermit.config.AntBuildModel;
import org.napile.idea.thermit.config.AntBuildModelBase;
import org.napile.idea.thermit.config.AntBuildTarget;
import org.napile.idea.thermit.config.ThermitConfigurationBase;
import org.napile.idea.thermit.dom.AntDomFileDescription;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.ide.macro.Macro;
import com.intellij.ide.macro.MacroManager;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.NewInstanceFactory;
import com.intellij.util.SystemProperties;
import com.intellij.util.config.AbstractProperty;
import com.intellij.util.config.BooleanProperty;
import com.intellij.util.config.ExternalizablePropertyContainer;
import com.intellij.util.config.IntProperty;
import com.intellij.util.config.ListProperty;
import com.intellij.util.config.StringProperty;
import com.intellij.util.config.ValueProperty;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.hash.LinkedHashMap;
public class AntBuildFileImpl implements AntBuildFileBase
{
private static final Logger LOG = Logger.getInstance("#org.napile.idea.thermit.config.impl.AntBuildFileImpl");
@NonNls
private static final String ANT_LIB = "/.thermit/lib";
private volatile Map<String, String> myCachedExternalProperties;
private final Object myOptionsLock = new Object();
public static final AbstractProperty<AntInstallation> ANT_INSTALLATION = new AbstractProperty<AntInstallation>()
{
public String getName()
{
return "$antInstallation";
}
public AntInstallation getDefault(final AbstractPropertyContainer container)
{
return GlobalThermitConfiguration.INSTANCE.get(container).getBundledAnt();
}
public AntInstallation copy(final AntInstallation value)
{
return value;
}
public AntInstallation get(final AbstractPropertyContainer container)
{
if(container.hasProperty(ANT_REFERENCE))
{
return RUN_WITH_ANT.get(container);
}
return GlobalThermitConfiguration.INSTANCE.get(container).getBundledAnt();
}
};
public static final AbstractProperty<List<File>> ALL_CLASS_PATH = new AbstractProperty<List<File>>()
{
public String getName()
{
return "$allClasspath";
}
public List<File> getDefault(AbstractProperty.AbstractPropertyContainer container)
{
return get(container);
}
public List<File> get(AbstractProperty.AbstractPropertyContainer container)
{
ArrayList<File> classpath = new ArrayList<File>();
collectClasspath(classpath, ADDITIONAL_CLASSPATH, container);
AntInstallation antInstallation = ANT_INSTALLATION.get(container);
if(antInstallation != null)
{
collectClasspath(classpath, AntInstallation.CLASS_PATH, antInstallation.getProperties());
}
return classpath;
}
private void collectClasspath(ArrayList<File> files, ListProperty<AntClasspathEntry> property, AbstractProperty.AbstractPropertyContainer container)
{
if(!container.hasProperty(property))
return;
Iterator<AntClasspathEntry> entries = property.getIterator(container);
while(entries.hasNext())
{
AntClasspathEntry entry = entries.next();
entry.addFilesTo(files);
}
}
public void set(AbstractProperty.AbstractPropertyContainer container, List<File> files)
{
throw new UnsupportedOperationException(getName());
}
public List<File> copy(List<File> files)
{
return files;
}
};
public static final BooleanProperty RUN_IN_BACKGROUND = new BooleanProperty("runInBackground", true);
public static final IntProperty MAX_HEAP_SIZE = new IntProperty("maximumHeapSize", 128);
public static final IntProperty MAX_STACK_SIZE = new IntProperty("maximumStackSize", 2);
public static final BooleanProperty VERBOSE = new BooleanProperty("verbose", true);
public static final BooleanProperty TREE_VIEW = new BooleanProperty("treeView", true);
public static final BooleanProperty CLOSE_ON_NO_ERRORS = new BooleanProperty("viewClosedWhenNoErrors", false);
public static final StringProperty CUSTOM_JDK_NAME = new StringProperty("customJdkName", "");
public static final ListProperty<TargetFilter> TARGET_FILTERS = ListProperty.create("targetFilters");
public static final ListProperty<BuildFileProperty> ANT_PROPERTIES = ListProperty.create("properties");
public static final StringProperty ANT_COMMAND_LINE_PARAMETERS = new StringProperty("antCommandLine", "");
public static final AbstractProperty<AntReference> ANT_REFERENCE = new ValueProperty<AntReference>("antReference", AntReference.PROJECT_DEFAULT);
public static final ListProperty<AntClasspathEntry> ADDITIONAL_CLASSPATH = ListProperty.create("additionalClassPath");
public static final AbstractProperty<AntInstallation> RUN_WITH_ANT = new AbstractProperty<AntInstallation>()
{
public String getName()
{
return "$runWithAnt";
}
@Nullable
public AntInstallation getDefault(AbstractProperty.AbstractPropertyContainer container)
{
return get(container);
}
@Nullable
public AntInstallation get(AbstractProperty.AbstractPropertyContainer container)
{
return AntReference.findAnt(ANT_REFERENCE, container);
}
public AntInstallation copy(AntInstallation antInstallation)
{
return antInstallation;
}
};
private final VirtualFile myVFile;
private final Project myProject;
private final ThermitConfigurationBase myAntConfiguration;
private final ExternalizablePropertyContainer myWorkspaceOptions;
private final ExternalizablePropertyContainer myProjectOptions;
private final AbstractProperty.AbstractPropertyContainer myAllOptions;
private final ClassLoaderHolder myClassloaderHolder;
private boolean myShouldExpand = true;
public AntBuildFileImpl(final XmlFile antFile, final ThermitConfigurationBase configuration)
{
myVFile = antFile.getOriginalFile().getVirtualFile();
myProject = antFile.getProject();
myAntConfiguration = configuration;
myWorkspaceOptions = new ExternalizablePropertyContainer();
myWorkspaceOptions.registerProperty(RUN_IN_BACKGROUND);
myWorkspaceOptions.registerProperty(CLOSE_ON_NO_ERRORS);
myWorkspaceOptions.registerProperty(TREE_VIEW);
myWorkspaceOptions.registerProperty(VERBOSE);
myWorkspaceOptions.registerProperty(TARGET_FILTERS, "filter", NewInstanceFactory.fromClass(TargetFilter.class));
myWorkspaceOptions.rememberKey(RUN_WITH_ANT);
myProjectOptions = new ExternalizablePropertyContainer();
myProjectOptions.registerProperty(MAX_HEAP_SIZE);
myProjectOptions.registerProperty(MAX_STACK_SIZE);
myProjectOptions.registerProperty(CUSTOM_JDK_NAME);
myProjectOptions.registerProperty(ANT_COMMAND_LINE_PARAMETERS);
myProjectOptions.registerProperty(ANT_PROPERTIES, "property", NewInstanceFactory.fromClass(BuildFileProperty.class));
myProjectOptions.registerProperty(ADDITIONAL_CLASSPATH, "entry", SinglePathEntry.EXTERNALIZER);
myProjectOptions.registerProperty(ANT_REFERENCE, AntReference.EXTERNALIZER);
myAllOptions = new CompositePropertyContainer(new AbstractProperty.AbstractPropertyContainer[]{
myWorkspaceOptions,
myProjectOptions,
GlobalThermitConfiguration.getInstance().getProperties(getProject())
});
myClassloaderHolder = new AntBuildFileClassLoaderHolder(myAllOptions);
}
public static List<File> getUserHomeLibraries()
{
ArrayList<File> classpath = new ArrayList<File>();
final String homeDir = SystemProperties.getUserHome();
new AllNZipsUnderDirEntry(new File(homeDir, ANT_LIB)).addFilesTo(classpath);
return classpath;
}
@Nullable
public String getPresentableName()
{
AntBuildModel model = myAntConfiguration.getModelIfRegistered(this);
String name = model != null ? model.getName() : null;
if(name == null || name.trim().length() == 0)
{
name = myVFile.getName();
}
return name;
}
@Nullable
public String getName()
{
final VirtualFile vFile = getVirtualFile();
return vFile != null ? vFile.getName() : null;
}
public AntBuildModelBase getModel()
{
return (AntBuildModelBase) myAntConfiguration.getModel(this);
}
@Nullable
public AntBuildModelBase getModelIfRegistered()
{
return (AntBuildModelBase) myAntConfiguration.getModelIfRegistered(this);
}
public boolean isRunInBackground()
{
return RUN_IN_BACKGROUND.value(myAllOptions);
}
@Nullable
public XmlFile getAntFile()
{
final PsiFile psiFile = myVFile.isValid() ? PsiManager.getInstance(getProject()).findFile(myVFile) : null;
if(!(psiFile instanceof XmlFile))
{
return null;
}
final XmlFile xmlFile = (XmlFile) psiFile;
return AntDomFileDescription.isAntFile(xmlFile) ? xmlFile : null;
}
public Project getProject()
{
return myProject;
}
@Nullable
public VirtualFile getVirtualFile()
{
return myVFile;
}
public AbstractProperty.AbstractPropertyContainer getAllOptions()
{
return myAllOptions;
}
@Nullable
public String getPresentableUrl()
{
final VirtualFile file = getVirtualFile();
return (file == null) ? null : file.getPresentableUrl();
}
public boolean shouldExpand()
{
return myShouldExpand;
}
public void setShouldExpand(boolean expand)
{
myShouldExpand = expand;
}
public boolean isTargetVisible(final AntBuildTarget target)
{
final TargetFilter filter = findFilter(target.getName());
if(filter == null)
{
return target.isDefault() || target.getNotEmptyDescription() != null;
}
return filter.isVisible();
}
public boolean exists()
{
final VirtualFile file = getVirtualFile();
if(file == null || !(new File(file.getPath()).exists()))
{
return false;
}
return true;
}
public void updateProperties()
{
// do not change position
final AntBuildTarget[] targets = getModel().getTargets();
final Map<String, AntBuildTarget> targetByName = new LinkedHashMap<String, AntBuildTarget>(targets.length);
for(AntBuildTarget target : targets)
{
String targetName = target.getName();
if(targetName != null)
{
targetByName.put(targetName, target);
}
}
synchronized(myOptionsLock)
{
myCachedExternalProperties = null;
final ArrayList<TargetFilter> filters = TARGET_FILTERS.getModifiableList(myAllOptions);
for(Iterator<TargetFilter> iterator = filters.iterator(); iterator.hasNext(); )
{
final TargetFilter filter = iterator.next();
final String name = filter.getTargetName();
if(name == null)
{
iterator.remove();
}
else
{
AntBuildTarget target = targetByName.get(name);
if(target != null)
{
filter.updateDescription(target);
targetByName.remove(name);
}
else
{
iterator.remove();
}
}
}
// handle the rest of targets with non-null names
for(AntBuildTarget target : targetByName.values())
{
filters.add(TargetFilter.fromTarget(target));
}
}
}
public void updateConfig()
{
basicUpdateConfig();
DaemonCodeAnalyzer.getInstance(getProject()).restart();
}
public void setTreeView(final boolean value)
{
TREE_VIEW.primSet(myAllOptions, value);
}
public void setVerboseMode(final boolean value)
{
VERBOSE.primSet(myAllOptions, value);
}
public boolean isViewClosedWhenNoErrors()
{
return CLOSE_ON_NO_ERRORS.value(myAllOptions);
}
public void readWorkspaceProperties(final Element parentNode) throws InvalidDataException
{
synchronized(myOptionsLock)
{
myWorkspaceOptions.readExternal(parentNode);
final Element expanded = parentNode.getChild("expanded");
if(expanded != null)
{
myShouldExpand = Boolean.valueOf(expanded.getAttributeValue("value"));
}
}
}
public void writeWorkspaceProperties(final Element parentNode) throws WriteExternalException
{
synchronized(myOptionsLock)
{
myWorkspaceOptions.writeExternal(parentNode);
final Element expandedElem = new Element("expanded");
expandedElem.setAttribute("value", Boolean.toString(myShouldExpand));
parentNode.addContent(expandedElem);
}
}
public void readProperties(final Element parentNode) throws InvalidDataException
{
synchronized(myOptionsLock)
{
myProjectOptions.readExternal(parentNode);
basicUpdateConfig();
readWorkspaceProperties(parentNode); // Compatibility with old Idea
}
}
public void writeProperties(final Element parentNode) throws WriteExternalException
{
synchronized(myOptionsLock)
{
myProjectOptions.writeExternal(parentNode);
}
}
private void basicUpdateConfig()
{
final XmlFile antFile = getAntFile();
if(antFile != null)
{
bindAnt();
myClassloaderHolder.updateClasspath();
}
}
@NotNull
public Map<String, String> getExternalProperties()
{
Map<String, String> result = myCachedExternalProperties;
if(result == null)
{
synchronized(myOptionsLock)
{
result = myCachedExternalProperties;
if(result == null)
{
result = new HashMap<String, String>();
final DataContext context = SimpleDataContext.getProjectContext(myProject);
final MacroManager macroManager = MacroManager.getInstance();
Iterator<BuildFileProperty> properties = ANT_PROPERTIES.getIterator(myAllOptions);
while(properties.hasNext())
{
BuildFileProperty property = properties.next();
try
{
String value = property.getPropertyValue();
value = macroManager.expandSilentMarcos(value, true, context);
value = macroManager.expandSilentMarcos(value, false, context);
result.put(property.getPropertyName(), value);
}
catch(Macro.ExecutionCancelledException e)
{
LOG.debug(e);
}
}
myCachedExternalProperties = result;
}
}
}
return result;
}
private void bindAnt()
{
ANT_REFERENCE.set(getAllOptions(), ANT_REFERENCE.get(getAllOptions()).bind(GlobalThermitConfiguration.getInstance()));
}
@Nullable
private TargetFilter findFilter(final String targetName)
{
final List<TargetFilter> filters;
synchronized(myOptionsLock)
{
filters = TARGET_FILTERS.get(myAllOptions);
}
for(TargetFilter targetFilter : filters)
{
if(Comparing.equal(targetName, targetFilter.getTargetName()))
{
return targetFilter;
}
}
return null;
}
@NotNull
public ClassLoader getClassLoader()
{
return myClassloaderHolder.getClassloader();
}
}