| package org.jetbrains.plugins.gradle.remote.impl; |
| |
| import com.intellij.openapi.externalSystem.model.DataNode; |
| import com.intellij.openapi.externalSystem.model.project.LibraryData; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.containers.ContainerUtilRt; |
| import com.intellij.util.containers.HashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import com.intellij.openapi.externalSystem.model.project.LibraryPathType; |
| |
| import java.io.File; |
| import java.util.*; |
| |
| /** |
| * Encapsulates logic of checking if particular collection of gradle libraries contains libraries with the same names and |
| * tries to diversify them in the case of the positive answer. |
| * <p/> |
| * Thread-safe. |
| * |
| * @author Denis Zhdanov |
| * @since 10/19/11 2:04 PM |
| */ |
| public class GradleLibraryNamesMixer { |
| |
| /** |
| * Holds mappings like <code>('file name'; boolean)</code> where <code>'file name'</code> defines 'too common' file/dir |
| * name that should not be used during library name generation. Boolean flag indicates if 'common file name' may be used |
| * if 'non-common' files are the same. |
| * <p/> |
| * Example: consider the following file system tree: |
| * <pre> |
| * module |
| * |_src |
| * |_main |
| * | |_resources |
| * | |
| * |_test |
| * |_resources |
| * </pre> |
| * Let's say we have two libraries where one of them points to <code>'src/main/resources'</code> and another one |
| * to <code>'src/test/resources'</code>. We want to generate names <code>'module-resources'</code> and |
| * <code>'module-test-resources'</code> respectively because <code>'test'</code> entry at the current collection is |
| * stored with <code>'true'</code> flag. |
| */ |
| private static final Map<String, Boolean> NON_UNIQUE_PATH_ENTRIES = new HashMap<String, Boolean>(); |
| static { |
| NON_UNIQUE_PATH_ENTRIES.put("src", false); |
| NON_UNIQUE_PATH_ENTRIES.put("main", false); |
| NON_UNIQUE_PATH_ENTRIES.put("test", true); |
| NON_UNIQUE_PATH_ENTRIES.put("resources", false); |
| NON_UNIQUE_PATH_ENTRIES.put("java", false); |
| NON_UNIQUE_PATH_ENTRIES.put("groovy", false); |
| } |
| private static final char NAME_SEPARATOR = '-'; |
| |
| /** |
| * Tries to ensure that given libraries have distinct names, i.e. traverses all of them and tries to generate |
| * unique name for those with equal names. |
| * |
| * @param libraries libraries to process |
| */ |
| @SuppressWarnings("MethodMayBeStatic") |
| public void mixNames(@NotNull Collection<DataNode<LibraryData>> libraries) { |
| if (libraries.isEmpty()) { |
| return; |
| } |
| Map<String, Wrapped> names = ContainerUtilRt.newHashMap(); |
| List<Wrapped> data = ContainerUtilRt.newArrayList(); |
| for (DataNode<LibraryData> library : libraries) { |
| Wrapped wrapped = new Wrapped(library.getData()); |
| data.add(wrapped); |
| } |
| boolean mixed = false; |
| while (!mixed) { |
| mixed = doMixNames(data, names); |
| } |
| } |
| |
| /** |
| * Does the same as {@link #mixNames(Collection)} but uses given <code>('library name; wrapped library'}</code> mappings cache. |
| * |
| * @param libraries libraries to process |
| * @param cache cache to use |
| * @return <code>true</code> if all of the given libraries have distinct names now; <code>false</code> otherwise |
| */ |
| private static boolean doMixNames(@NotNull Collection<Wrapped> libraries, @NotNull Map<String, Wrapped> cache) { |
| cache.clear(); |
| for (Wrapped current : libraries) { |
| Wrapped previous = cache.remove(current.library.getExternalName()); |
| if (previous == null) { |
| cache.put(current.library.getExternalName(), current); |
| } |
| else { |
| mixNames(current, previous); |
| return current.library.getExternalName().equals(previous.library.getExternalName()); // Stop processing if it's not possible to generate |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Tries to generate distinct names for the given wrapped libraries (assuming that they have equal names at the moment). |
| * |
| * @param wrapped1 one of the libraries with equal names |
| * @param wrapped2 another library which name is equal to the name of the given one |
| */ |
| @SuppressWarnings("AssignmentToForLoopParameter") |
| private static void mixNames(@NotNull Wrapped wrapped1, @NotNull Wrapped wrapped2) { |
| if (!wrapped1.prepare() || !wrapped2.prepare()) { |
| return; |
| } |
| String wrapped1AltText = null; |
| String wrapped2AltText = null; |
| |
| for (File file1 = wrapped1.currentFile, file2 = wrapped2.currentFile; |
| file1 != null && file2 != null; |
| file1 = file1.getParentFile(), file2 = file2.getParentFile()) |
| { |
| while (file1 != null && !StringUtil.isEmpty(file1.getName()) && NON_UNIQUE_PATH_ENTRIES.containsKey(file1.getName())) { |
| if (NON_UNIQUE_PATH_ENTRIES.get(file1.getName())) { |
| if (StringUtil.isEmpty(wrapped1AltText)) { |
| wrapped1AltText = file1.getName(); |
| } |
| else { |
| wrapped1AltText += NAME_SEPARATOR + file1.getName(); |
| } |
| } |
| file1 = file1.getParentFile(); |
| } |
| while (file2 != null && !StringUtil.isEmpty(file2.getName()) && NON_UNIQUE_PATH_ENTRIES.containsKey(file2.getName())) { |
| if (NON_UNIQUE_PATH_ENTRIES.get(file2.getName())) { |
| if (StringUtil.isEmpty(wrapped2AltText)) { |
| wrapped2AltText = file2.getName(); |
| } |
| else { |
| wrapped2AltText += NAME_SEPARATOR + file2.getName(); |
| } |
| } |
| file2 = file2.getParentFile(); |
| } |
| |
| if (file1 == null) { |
| wrapped1.nextFile(); |
| } |
| else if (!wrapped1.library.getExternalName().startsWith(file1.getName())) { |
| wrapped1.library.setExternalName(file1.getName() + NAME_SEPARATOR + wrapped1.library.getExternalName()); |
| } |
| if (file2 == null) { |
| wrapped2.nextFile(); |
| } |
| else if (!wrapped2.library.getExternalName().startsWith(file2.getName())) { |
| wrapped2.library.setExternalName(file2.getName() + NAME_SEPARATOR + wrapped2.library.getExternalName()); |
| } |
| |
| if (wrapped1.library.getExternalName().equals(wrapped2.library.getExternalName())) { |
| if (wrapped1AltText != null) { |
| diversifyName(wrapped1AltText, wrapped1, file1); |
| return; |
| } |
| else if (wrapped2AltText != null) { |
| diversifyName(wrapped2AltText, wrapped2, file1); |
| return; |
| } |
| } |
| else { |
| return; |
| } |
| |
| if (file1 == null || file2 == null) { |
| return; |
| } |
| } |
| } |
| |
| @SuppressWarnings("ConstantConditions") |
| private static void diversifyName(@NotNull String changeText, @NotNull Wrapped wrapped, @Nullable File file) { |
| String name = wrapped.library.getExternalName(); |
| int i = file == null ? - 1 : name.indexOf(file.getName()); |
| final String newName; |
| if (i >= 0) { |
| newName = name.substring(0, i + file.getName().length()) + NAME_SEPARATOR + changeText + name.substring(i + file.getName().length()); |
| } |
| else { |
| newName = changeText + NAME_SEPARATOR + name; |
| } |
| wrapped.library.setExternalName(newName); |
| } |
| |
| /** |
| * Wraps target library and hold auxiliary information required for the processing. |
| */ |
| private static class Wrapped { |
| /** Holds list of files that may be used for name generation. */ |
| public final Set<File> files = new HashSet<File>(); |
| /** File that was used for the current name generation. */ |
| public File currentFile; |
| /** Target library. */ |
| public LibraryData library; |
| |
| Wrapped(@NotNull LibraryData library) { |
| this.library = library; |
| for (LibraryPathType pathType : LibraryPathType.values()) { |
| for (String path : library.getPaths(pathType)) { |
| files.add(new File(path)); |
| } |
| } |
| } |
| |
| public boolean prepare() { |
| if (currentFile != null) { |
| return true; |
| } |
| return nextFile(); |
| } |
| |
| public boolean nextFile() { |
| if (files.isEmpty()) { |
| return false; |
| } |
| Iterator<File> iterator = files.iterator(); |
| currentFile = iterator.next(); |
| iterator.remove(); |
| return true; |
| } |
| } |
| } |