blob: 8e3bb4f7548e235a76ba7f60ba57fd5253b253b9 [file] [log] [blame]
/*
* 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.configurations;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.api.MavenConfiguration;
import static org.netbeans.modules.maven.configurations.Bundle.*;
import org.netbeans.modules.maven.execute.model.ActionToGoalMapping;
import org.netbeans.modules.maven.execute.model.NetbeansActionMapping;
import org.netbeans.modules.maven.execute.model.NetbeansActionProfile;
import org.netbeans.modules.maven.execute.model.NetbeansActionReader;
import org.netbeans.modules.maven.execute.model.io.xpp3.NetbeansBuildActionXpp3Writer;
import org.netbeans.modules.maven.spi.actions.AbstractMavenActionsProvider;
import org.netbeans.modules.maven.spi.actions.MavenActionsProvider;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle.Messages;
/**
* Represents a configuration, Properties defined by {@link MavenConfiguration} plus list of action descriptions
* for that configuration. This class is used to represent:
* <ul>
* <li>default configurations, provided by Maven project.
* <li>profile-based configurations
* <li>user-customized configurations, that loads action configs from the project directory.
* </ul>
*
* @author mkleint
*/
public class M2Configuration extends AbstractMavenActionsProvider implements MavenConfiguration, Comparable<M2Configuration> {
private static final Logger LOG = Logger.getLogger(M2Configuration.class.getName());
public static final String DEFAULT = "%%DEFAULT%%"; //NOI18N
public static final String FILENAME = "nbactions.xml"; //NOI18N
public static final String FILENAME_PREFIX = "nbactions-"; //NOI18N
public static final String FILENAME_SUFFIX = ".xml"; //NOI18N
public static M2Configuration createDefault(FileObject projectDirectory) {
return new M2Configuration(DEFAULT, projectDirectory);
}
/**
* True, if the M2Configuration comes from a customized project storage.
*/
private boolean customized;
private @NonNull final String id;
private List<String> profiles;
private final Map<String,String> properties = new HashMap<String,String>();
private final FileObject projectDirectory;
private final AtomicBoolean resetCache = new AtomicBoolean(false);
private FileChangeListener listener = null;
private String displayName;
public M2Configuration(String id, FileObject projectDirectory) {
assert id != null;
this.id = id;
this.projectDirectory = projectDirectory;
profiles = Collections.<String>emptyList();
}
@Override
@Messages("TXT_DefaultConfig=<default config>")
public String getDisplayName() {
if (DEFAULT.equals(id)) {
return TXT_DefaultConfig();
}
return displayName != null ? displayName : id;
}
void setDisplayName(String displayName) {
this.displayName = displayName;
}
public @NonNull String getId() {
return id;
}
public void setActivatedProfiles(List<String> profs) {
profiles = profs;
}
@Override
public List<String> getActivatedProfiles() {
return profiles;
}
@Override
public Map<String,String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> props) {
if (props == null) {
props = Collections.emptyMap();
}
properties.clear();
properties.putAll(props);
}
public static String getFileNameExt(String id) {
if (DEFAULT.equals(id)) {
return FILENAME;
}
return FILENAME_PREFIX + id + FILENAME_SUFFIX;
}
public @Override boolean equals(Object obj) {
return obj instanceof M2Configuration && id.equals(((M2Configuration) obj).id);
}
public @Override int hashCode() {
return id.hashCode();
}
@Override public String toString() {
return id;
}
public @Override int compareTo(M2Configuration o) {
return id.compareTo(o.id);
}
public @Override InputStream getActionDefinitionStream() {
return getActionDefinitionStream(id);
}
public boolean isCustomized() {
return customized;
}
final InputStream getActionDefinitionStream(String forId) {
checkListener();
FileObject fo = projectDirectory.getFileObject(getFileNameExt(forId));
customized = fo != null;
resetCache.set(false);
if (fo != null) {
try {
return fo.getInputStream();
} catch (FileNotFoundException ex) {
LOG.log(Level.WARNING, "Cannot read " + fo, ex); // NOI18N
}
}
return null;
}
private synchronized void checkListener() {
final String fid = getFileNameExt(id);
if (listener == null) {
listener = new FileChangeAdapter() {
@Override
public void fileRenamed(FileRenameEvent fe) {
if (fid.equals(fe.getName() + "." + fe.getExt())) {
resetCache();
}
}
@Override
public void fileDeleted(FileEvent fe) {
if (fid.equals(fe.getFile().getNameExt())) {
resetCache();
}
}
@Override
public void fileChanged(FileEvent fe) {
if (fid.equals(fe.getFile().getNameExt())) {
resetCache();
}
}
@Override
public void fileDataCreated(FileEvent fe) {
if (fid.equals(fe.getFile().getNameExt())) {
resetCache();
}
}
};
projectDirectory.addFileChangeListener(FileUtil.weakFileChangeListener(listener, projectDirectory));
}
}
private void resetCache() {
resetCache.compareAndSet(false, true);
}
/**
* get custom action maven mapping configuration
* No replacements happen.
* The instances returned is always a new copy, can be modified or reused.
* Same method in NbGlobalActionGolaProvider
*/
public NetbeansActionMapping[] getCustomMappings() {
NetbeansActionMapping[] fallbackActions = new NetbeansActionMapping[0];
try {
List<NetbeansActionMapping> toRet = new ArrayList<NetbeansActionMapping>();
// just a converter for the To-Object reader..
Reader read = performDynamicSubstitutions(Collections.<String,String>emptyMap(), getRawMappingsAsString());
// basically doing a copy here..
ActionToGoalMapping mapping = reader.read(read);
for (NetbeansActionMapping mapp : mapping.getActions()) {
if (mapp.getActionName().startsWith("CUSTOM-")) { //NOI18N
toRet.add(mapp);
}
}
return toRet.toArray(new NetbeansActionMapping[toRet.size()]);
} catch (XmlPullParserException ex) {
LOG.log(Level.WARNING, null, ex);
} catch (IOException ex) {
LOG.log(Level.WARNING, null, ex);
}
return fallbackActions;
}
@Override
protected boolean reloadStream() {
return resetCache.get();
}
@Override
public ActionToGoalMapping getRawMappings() {
if (originalMappings == null || reloadStream()) {
Reader rdr = null;
InputStream in = getActionDefinitionStream();
try {
if (in == null) {
in = getActionDefinitionStream(DEFAULT);
if (in != null) {
rdr = new InputStreamReader(in);
ActionToGoalMapping def = reader.read(rdr);
for (NetbeansActionProfile p : def.getProfiles()) {
if (id.equals(p.getId()) && p.getActions() != null) {
Map<String, Integer> posMap = new HashMap<>();
for (int i = 0; i < def.getActions().size(); i++) {
posMap.put(def.getActions().get(i).getActionName(), i);
}
// merge in or override global actions:
for (NetbeansActionMapping am : p.getActions()) {
String n = am.getActionName();
Integer i = posMap.get(n);
if (null == i) {
def.getActions().add(am);
} else {
def.getActions().set(i, am);
}
}
break;
}
}
originalMappings = def;
} else {
originalMappings = new ActionToGoalMapping();
}
} else {
rdr = new InputStreamReader(in);
originalMappings = reader.read(rdr);
}
} catch (IOException ex) {
LOG.log(Level.INFO, "Loading raw mappings", ex);
} catch (XmlPullParserException ex) {
LOG.log(Level.INFO, "Loading raw mappings", ex);
} finally {
if (rdr != null) {
try {
rdr.close();
} catch (IOException ex) {
}
}
}
if (originalMappings == null) {
originalMappings = new ActionToGoalMapping();
}
}
return originalMappings;
}
public NetbeansActionMapping getProfileMappingForAction(
String action, Project project,
Map<String,String> replaceMap, boolean[] fallback
) {
NetbeansActionReader parsed = new NetbeansActionReader() {
@Override
protected String getRawMappingsAsString() {
NetbeansBuildActionXpp3Writer writer = new NetbeansBuildActionXpp3Writer();
StringWriter str = new StringWriter();
InputStreamReader rdr = null;
try {
// relay the action load to the possibly customized getRawMappings
ActionToGoalMapping map = getRawMappings();
writer.write(str, map);
} catch (IOException ex) {
LOG.log(Level.WARNING, "Loading raw mappings", ex);
} finally {
if(rdr != null) {
try {
rdr.close();
} catch (IOException ex) {
}
}
}
return str.toString();
}
@Override
protected Reader performDynamicSubstitutions(Map<String, String> replaceMap, String in) throws IOException {
return M2Configuration.this.performDynamicSubstitutions(replaceMap, in);
}
};
NetbeansActionMapping ret = parsed.getMappingForAction(reader, LOG, action, null, project, id, replaceMap);
if (ret == null) {
boolean[] hasProfiles = { false };
ret = parsed.getMappingForAction(reader, LOG, action, hasProfiles, project, null, replaceMap);
if (fallback != null && ret != null && hasProfiles[0]) {
fallback[0] = true;
}
}
return ret;
}
public NetbeansActionMapping findMappingFor(
Map<String, String> replaceMap, Project project, String actionName,
boolean[] fallback
) {
NetbeansActionMapping action = getProfileMappingForAction(
actionName, project, replaceMap, fallback
);
if (action != null) {
return action;
}
return new NetbeansActionReader() {
@Override
protected String getRawMappingsAsString() {
return M2Configuration.this.getRawMappingsAsString();
}
@Override
protected Reader performDynamicSubstitutions(Map<String, String> replaceMap, String in) throws IOException {
return M2Configuration.this.performDynamicSubstitutions(replaceMap, in);
}
}.getMappingForAction(reader, LOG, actionName, null, project, null, replaceMap);
}
}