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 }