package org.netbeans.gradle.project.view; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.model.GradleTaskID; import org.netbeans.gradle.model.util.CollectionUtils; import org.netbeans.gradle.model.util.MultiMapUtils; public final class GradleTaskTree { private static final Logger LOGGER = Logger.getLogger(GradleTaskTree.class.getName()); private static final int DEFAULT_TASK_LIMIT = 20; private final String caption; private final GradleTaskID taskID; private final List<GradleTaskTree> children; public GradleTaskTree(GradleTaskID taskID) { ExceptionHelper.checkNotNullArgument(taskID, "taskID"); this.caption = taskID.getName(); this.taskID = taskID; this.children = Collections.emptyList(); } public GradleTaskTree(String caption, List<GradleTaskTree> children) { this(caption, null, children); } private GradleTaskTree( String caption, GradleTaskID taskID, List<GradleTaskTree> children) { ExceptionHelper.checkNotNullArgument(caption, "caption"); this.caption = caption; this.taskID = taskID; this.children = CollectionUtils.copyNullSafeList(children); } public String getCaption() { return caption; } @Nullable public GradleTaskID getTaskID() { return taskID; } public List<GradleTaskTree> getChildren() { return children; } public static List<GradleTaskTree> createTaskTree(Collection<GradleTaskID> tasks) { return createTaskTree(DEFAULT_TASK_LIMIT, tasks); } public static List<GradleTaskTree> createTaskTree( int taskLimit, Collection<GradleTaskID> taskIDs) { return createTaskTree(taskLimit, 0, taskIDs); } private static List<GradleTaskTree> createTaskTree( int taskLimit, int skipCharCount, Collection<GradleTaskID> taskIDs) { int taskCount = taskIDs.size(); if (taskCount <= taskLimit || taskCount <= 1) { return toLeafs(taskIDs); } Map<String, List<GradleTaskID>> splitTasks = new LinkedHashMap<>(); for (GradleTaskID taskID: taskIDs) { String name = taskID.getName(); int nextPos = getNextWordStartPos(skipCharCount, name); if (nextPos < 0) { MultiMapUtils.addToMultiMap(name, taskID, splitTasks); } else { String prefix = name.substring(0, nextPos); MultiMapUtils.addToMultiMap(prefix, taskID, splitTasks); } } int splitTasksSize = splitTasks.size(); if (splitTasksSize == 1) { Map.Entry<String, List<GradleTaskID>> task = splitTasks.entrySet().iterator().next(); int nextSkipCharCount = task.getKey().length(); return createTaskTree(taskLimit, nextSkipCharCount, taskIDs); } else { List<GradleTaskTree> result = new ArrayList<>(splitTasksSize); for (Map.Entry<String, List<GradleTaskID>> entry: splitTasks.entrySet()) { GradleTaskTree subTree = createTaskTree(taskLimit, entry); if (subTree != null) { result.add(subTree); } } int resultSize = result.size(); if (resultSize > taskLimit) { LOGGER.log(Level.INFO, "Number of tasks exceed the needed limit. Task count: {0}. Requested limit: {1}.", new Object[]{resultSize, taskLimit}); } return result; } } private static GradleTaskTree createTaskTree( int taskLimit, Map.Entry<String, List<GradleTaskID>> entry) { List<GradleTaskID> childTasks = entry.getValue(); int childrenCount = childTasks.size(); if (childrenCount > 1) { String key = entry.getKey(); List<GradleTaskTree> subTrees = createTaskTree(taskLimit, key.length(), childTasks); return new GradleTaskTree(key, subTrees); } else if (childrenCount == 1) { return new GradleTaskTree(childTasks.get(0)); } else { return null; } } private static List<GradleTaskTree> toLeafs(Collection<GradleTaskID> taskIDs) { List<GradleTaskTree> result = new ArrayList<>(taskIDs.size()); for (GradleTaskID taskID: taskIDs) { result.add(new GradleTaskTree(taskID)); } return result; } private static CharacterType getCharacterType(char ch) { switch (Character.getType(ch)) { case Character.UPPERCASE_LETTER: return CharacterType.UPPERCASE_LETTER; case Character.LOWERCASE_LETTER: return CharacterType.LOWERCASE_LETTER; case Character.DECIMAL_DIGIT_NUMBER: return CharacterType.NUMBER; default: return CharacterType.OTHER; } } private static int getNextWordStartPos(int startPos, String str) { int length = str.length(); if (length <= startPos + 1) { // We need at least two characters to have a chance for a next word. return -1; } CharacterType firstCh = getCharacterType(str.charAt(startPos)); CharacterType secondCh = getCharacterType(str.charAt(startPos + 1)); int startOffset; CharacterType typeToAvoid; if (firstCh == CharacterType.UPPERCASE_LETTER) { if (!secondCh.letter) { return startPos + 1; } typeToAvoid = secondCh; startOffset = startPos + 2; } else { typeToAvoid = firstCh; startOffset = startPos + 1; } if (typeToAvoid == CharacterType.OTHER) { for (int i = startPos + 1; i < length; i++) { typeToAvoid = getCharacterType(str.charAt(i)); if (typeToAvoid != CharacterType.OTHER) { startOffset = i + 1; break; } } if (typeToAvoid == CharacterType.OTHER) { return -1; } } for (int i = startOffset; i < length; i++) { if (typeToAvoid != getCharacterType(str.charAt(i))) { return i; } } return -1; } private enum CharacterType { LOWERCASE_LETTER(true), UPPERCASE_LETTER(true), NUMBER(false), OTHER(false); public final boolean letter; private CharacterType(boolean letter) { this.letter = letter; } } }