1 /* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.tools.jlink.internal.plugins; 26 27 import java.nio.file.Path; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.EnumSet; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Optional; 35 import java.util.Set; 36 37 import jdk.tools.jlink.internal.ModuleSorter; 38 import jdk.tools.jlink.internal.Utils; 39 import jdk.tools.jlink.plugin.Plugin; 40 import jdk.tools.jlink.plugin.PluginException; 41 import jdk.tools.jlink.plugin.ResourcePool; 42 import jdk.tools.jlink.plugin.ResourcePoolBuilder; 43 import jdk.tools.jlink.plugin.ResourcePoolEntry; 44 import jdk.tools.jlink.plugin.ResourcePoolEntry.Type; 45 import jdk.tools.jlink.plugin.ResourcePoolModule; 46 47 /** 48 * A plugin to de-duplicate the legal notices from JMOD files. 49 * 50 * For a de-duplicated legal notice, the actual copy will be in 51 * the base module and with symbolic links in other modules. 52 * On platform that does not support symbolic links, a file 53 * will be created to contain the path to the linked target. 54 */ 55 public final class LegalNoticeFilePlugin implements Plugin { 56 57 private static final String NAME = "dedup-legal-notices"; 58 private static final String ERROR_IF_NOT_SAME_CONTENT = "error-if-not-same-content"; 59 private final Map<String, List<ResourcePoolEntry>> licenseOrNotice = 60 new HashMap<>(); 61 62 private boolean errorIfNotSameContent = false; 63 64 @Override 65 public String getName() { 66 return NAME; 67 } 68 69 @Override 70 public Set<State> getState() { 71 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL); 72 } 73 74 @Override 75 public void configure(Map<String, String> config) { 76 String arg = config.get(NAME); 77 if (arg != null) { 78 if (arg.equals(ERROR_IF_NOT_SAME_CONTENT)) { 79 errorIfNotSameContent = true; 80 } else { 81 throw new IllegalArgumentException(NAME + ": " + arg); 82 } 83 } 84 } 85 86 @Override 87 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { 88 // Sort modules in the topological order 89 // process all legal notices/licenses entries 90 new ModuleSorter(in.moduleView()) 91 .sorted() 92 .flatMap(ResourcePoolModule::entries) 93 .filter(entry -> entry.type() == Type.LEGAL_NOTICE) 94 .forEach(this::dedupLegalNoticeEntry); 95 96 in.entries() 97 .filter(entry -> entry.type() != Type.LEGAL_NOTICE) 98 .forEach(out::add); 99 100 licenseOrNotice.values().stream() 101 .flatMap(List::stream) 102 .forEach(out::add); 103 return out.build(); 104 } 105 106 private void dedupLegalNoticeEntry(ResourcePoolEntry entry) { 107 Path path = Utils.getJRTFSPath(entry.path()); 108 Path filename = path.getFileName(); 109 110 List<ResourcePoolEntry> entries = 111 licenseOrNotice.computeIfAbsent(filename.toString(), _k -> new ArrayList<>()); 112 113 Optional<ResourcePoolEntry> otarget = entries.stream() 114 .filter(e -> e.linkedTarget() == null) 115 .filter(e -> Arrays.equals(e.contentBytes(), entry.contentBytes())) 116 .findFirst(); 117 if (!otarget.isPresent()) { 118 if (errorIfNotSameContent) { 119 // all legal notices of the same file name are expected 120 // to contain the same content 121 Optional<ResourcePoolEntry> ores = 122 entries.stream().filter(e -> e.linkedTarget() == null) 123 .findAny(); 124 125 if (ores.isPresent()) { 126 throw new PluginException(ores.get().path() + " " + 127 entry.path() + " contain different content"); 128 } 129 } 130 entries.add(entry); 131 } else { 132 entries.add(ResourcePoolEntry.createSymLink(entry.path(), 133 entry.type(), 134 otarget.get())); 135 } 136 } 137 138 @Override 139 public Category getType() { 140 return Category.TRANSFORMER; 141 } 142 143 @Override 144 public String getDescription() { 145 return PluginsResourceBundle.getDescription(NAME); 146 } 147 148 @Override 149 public boolean hasArguments() { 150 return true; 151 } 152 153 @Override 154 public String getArgumentsDescription() { 155 return PluginsResourceBundle.getArgument(NAME); 156 } 157 }