/**
* Copyright 2016-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* 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 net.roboconf.tooling.core.autocompletion;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.roboconf.core.Constants;
import net.roboconf.core.dsl.ParsingConstants;
import net.roboconf.core.dsl.converters.FromGraphDefinition;
import net.roboconf.core.model.beans.AbstractType;
import net.roboconf.core.model.beans.Facet;
import net.roboconf.core.model.beans.Graphs;
import net.roboconf.core.model.helpers.ComponentHelpers;
import net.roboconf.core.utils.Utils;
import net.roboconf.tooling.core.autocompletion.ICompletionProposer.RoboconfCompletionProposal;
/**
* @author Vincent Zurczak - Linagora
*/
public final class CompletionUtils {
public static final String DEFAULT_VALUE = "Default value: ";
public static final String SET_BY_ROBOCONF = "Value set dynamically by Roboconf";
public static final String IMPORT_ALL_THE_VARIABLES = "Import all the variables";
/**
* Private empty constructor.
*/
private CompletionUtils() {
// nothing
}
/**
* Finds all the graph files that can be imported.
* @param appDirectory the application's directory
* @param editedFile the graph file that is being edited
* @param fileContent the file content (not null)
* @return a non-null set of (relative) file paths
*/
public static Set<String> findGraphFilesToImport( File appDirectory, File editedFile, String fileContent ) {
File graphDir = new File( appDirectory, Constants.PROJECT_DIR_GRAPH );
return findFilesToImport( graphDir, Constants.FILE_EXT_GRAPH, editedFile, fileContent );
}
/**
* Finds all the instances files that can be imported.
* @param appDirectory the application's directory
* @param editedFile the graph file that is being edited
* @param fileContent the file content (not null)
* @return a non-null set of (relative) file paths
*/
public static Set<String> findInstancesFilesToImport( File appDirectory, File editedFile, String fileContent ) {
File instancesDir = new File( appDirectory, Constants.PROJECT_DIR_INSTANCES );
return findFilesToImport( instancesDir, Constants.FILE_EXT_INSTANCES, editedFile, fileContent );
}
/**
* Finds all the files that can be imported.
* @param searchDirectory the search's directory
* @param fileExtension the file extension to search for
* @param editedFile the graph file that is being edited
* @param fileContent the file content (not null)
* @return a non-null set of (relative) file paths
*/
static Set<String> findFilesToImport(
File searchDirectory,
String fileExtension,
File editedFile,
String fileContent ) {
// Find all the files
Set<String> result = new TreeSet<> ();
if( searchDirectory.exists()) {
for( File f : Utils.listAllFiles( searchDirectory, fileExtension )) {
if( f.equals( editedFile ))
continue;
String path = Utils.computeFileRelativeLocation( searchDirectory, f );
result.add( path );
}
}
// Remove those that are already imported
Pattern importPattern = Pattern.compile( "\\b" + ParsingConstants.KEYWORD_IMPORT + "\\s+(.*)", Pattern.CASE_INSENSITIVE );
Matcher m = importPattern.matcher( fileContent );
while( m.find()) {
String imp = m.group( 1 ).trim();
if( imp.endsWith( ";" ))
imp = imp.substring( 0, imp.length() - 1 );
result.remove( imp.trim());
}
return result;
}
/**
* A convenience method to shorten the creation of a basic proposal.
* @param s
* @param lastWord
* @param trim
* @return a non-null proposal
*/
public static RoboconfCompletionProposal basicProposal( String s, String lastWord, boolean trim ) {
return new RoboconfCompletionProposal( s, trim ? s.trim() : s, null, lastWord.length());
}
/**
* A convenience method to shorten the creation of a basic proposal.
* <p>
* Equivalent to <code>basicProposal( s, lastWord, false )</code>.
* </p>
*
* @param s
* @param lastWord
* @return a non-null proposal
*/
public static RoboconfCompletionProposal basicProposal( String s, String lastWord ) {
return basicProposal( s, lastWord, false );
}
/**
* @param s (not null)
* @param prefix (not null)
* @return true if s starts with prefix (case insensitively)
*/
public static boolean startsWith( String s, String prefix ) {
return s.toLowerCase().startsWith( prefix.toLowerCase());
}
/**
* Finds all the Roboconf types.
* @param appDirectory the application's directory (can be null)
* @return a non-null map of types (key = type name, value = type)
*/
public static Map<String,RoboconfTypeBean> findAllTypes( File appDirectory ) {
List<File> graphFiles = new ArrayList<> ();
File graphDirectory = appDirectory;
if( graphDirectory != null
&& graphDirectory.exists())
graphFiles = Utils.listAllFiles( graphDirectory, Constants.FILE_EXT_GRAPH );
Map<String,RoboconfTypeBean> result = new HashMap<> ();
for( File f : graphFiles ) {
try {
FromGraphDefinition converter = new FromGraphDefinition( appDirectory, true );
Graphs g = converter.buildGraphs( f );
Collection<AbstractType> types = new ArrayList<> ();
types.addAll( ComponentHelpers.findAllComponents( g ));
types.addAll( g.getFacetNameToFacet().values());
for( AbstractType type : types ) {
RoboconfTypeBean bean = new RoboconfTypeBean(
type.getName(),
converter.getTypeAnnotations().get( type.getName()),
type instanceof Facet );
result.put( type.getName(), bean );
for( Map.Entry<String,String> entry : ComponentHelpers.findAllExportedVariables( type ).entrySet()) {
bean.exportedVariables.put( entry.getKey(), entry.getValue());
}
}
} catch( Exception e ) {
Logger logger = Logger.getLogger( CompletionUtils.class.getName());
Utils.logException( logger, e );
}
}
return result;
}
/**
* @author Vincent Zurczak - Linagora
*/
public final static class RoboconfTypeBean {
public final Map<String,String> exportedVariables = new TreeMap<> ();
private final String name, description;
private final boolean facet;
/**
* Constructor.
* @param name
* @param description
* @param facet
*/
private RoboconfTypeBean( String name, String description, boolean facet ) {
this.name = name;
this.description = description;
this.facet = facet;
}
public String getName() {
return this.name;
}
public String getDescription() {
return this.description;
}
public boolean isFacet() {
return this.facet;
}
}
/**
* Resolves the description to show for an exported variable.
* @param variableName a non-null variable name
* @param defaultValue the default value (can be null)
* @return a description (can be null)
*/
public static String resolveStringDescription( String variableName, String defaultValue ) {
String result = null;
if( Constants.SPECIFIC_VARIABLE_IP.equalsIgnoreCase( variableName )
|| variableName.toLowerCase().endsWith( "." + Constants.SPECIFIC_VARIABLE_IP ))
result = SET_BY_ROBOCONF;
else if( ! Utils.isEmptyOrWhitespaces( defaultValue ))
result = DEFAULT_VALUE + defaultValue;
return result;
}
/**
* Find exported variables from raw graph files (including malformed ones).
* * @param appDirectory the application's directory (can be null)
* @return a non-null map of exported variables (key = name, value = default value)
*/
public static Map<String,String> findAllExportedVariables( File appDirectory ) {
// TreeMap: keys are sorted alphabetically.
Map<String,String> result = new TreeMap<> ();
for( RoboconfTypeBean type : findAllTypes( appDirectory ).values()) {
if( type.exportedVariables.size() > 0 )
result.put( type.getName() + ".*", IMPORT_ALL_THE_VARIABLES );
for( Map.Entry<String,String> entry : type.exportedVariables.entrySet()) {
String desc = resolveStringDescription( entry.getKey(), entry.getValue());
result.put( entry.getKey(), desc );
}
}
return result;
}
/**
* Find type names from raw graph files (including malformed ones).
* @param appDirectory the application's directory (can be null)
* @param includeFacets true to include facet names in the result
* @param includeComponents true to include component names in the result
* @return a non-null map (key = type name, value = description)
*/
public static Map<String,String> findTypeNames( File appDirectory, boolean includeFacets, boolean includeComponents ) {
// TreeMap: keys are sorted alphabetically.
Map<String,String> result = new TreeMap<> ();
for( RoboconfTypeBean type : findAllTypes( appDirectory ).values()) {
if( includeFacets && type.isFacet())
result.put( type.getName(), type.getDescription());
if( includeComponents && ! type.isFacet())
result.put( type.getName(), type.getDescription());
}
return result;
}
/**
* Builds proposals from a map (key = replacement, value = description).
* @param viewer the viewer
* @param candidates a non-null list of candidates
* @param prefix a non-null prefix
* @param offset the viewer's offset
* @return a non-null list of proposals
*/
public static List<RoboconfCompletionProposal> buildProposalsFromMap( Map<String,String> candidates, String prefix ) {
List<RoboconfCompletionProposal> result = new ArrayList<> ();
for( Map.Entry<String,String> entry : candidates.entrySet()) {
// Matching candidate?
String candidate = entry.getKey();
if( ! startsWith( candidate, prefix ))
continue;
// No description => basic proposal
if( Utils.isEmptyOrWhitespaces( entry.getValue())) {
result.add( basicProposal( candidate, prefix ));
}
// Otherwise, show the description
else {
result.add( new RoboconfCompletionProposal(
candidate,
candidate,
entry.getValue(),
prefix.length()));
}
}
return result;
}
}