1/*
2 * Copyright (c) 2015, 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 */
25package jdk.tools.jlink.internal.plugins;
26
27import java.io.File;
28import java.io.IOException;
29import java.io.UncheckedIOException;
30import java.nio.file.FileSystem;
31import java.nio.file.Files;
32import java.nio.file.PathMatcher;
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Map;
37import java.util.function.ToIntFunction;
38import jdk.tools.jlink.plugin.PluginException;
39import jdk.tools.jlink.plugin.ResourcePool;
40import jdk.tools.jlink.plugin.ResourcePoolBuilder;
41import jdk.tools.jlink.plugin.ResourcePoolEntry;
42import jdk.tools.jlink.plugin.Plugin;
43import jdk.tools.jlink.internal.Utils;
44
45/**
46 *
47 * Order Resources plugin
48 */
49public final class OrderResourcesPlugin implements Plugin {
50    public static final String NAME = "order-resources";
51    private static final FileSystem JRT_FILE_SYSTEM = Utils.jrtFileSystem();
52
53    private final List<ToIntFunction<String>> filters;
54    private final Map<String, Integer> orderedPaths;
55
56    public OrderResourcesPlugin() {
57        this.filters = new ArrayList<>();
58        this.orderedPaths = new HashMap<>();
59    }
60
61    @Override
62    public String getName() {
63        return NAME;
64    }
65
66    static class SortWrapper {
67        private final ResourcePoolEntry resource;
68        private final int ordinal;
69
70        SortWrapper(ResourcePoolEntry resource, int ordinal) {
71            this.resource = resource;
72            this.ordinal = ordinal;
73        }
74
75        ResourcePoolEntry getResource() {
76            return resource;
77        }
78
79        String getPath() {
80            return resource.path();
81        }
82
83        int getOrdinal() {
84            return ordinal;
85        }
86    }
87
88    private String stripModule(String path) {
89        if (path.startsWith("/")) {
90            int index = path.indexOf('/', 1);
91
92            if (index != -1) {
93                return path.substring(index + 1);
94            }
95        }
96
97        return path;
98    }
99
100    private int getOrdinal(ResourcePoolEntry resource) {
101        String path = resource.path();
102
103        Integer value = orderedPaths.get(stripModule(path));
104
105        if (value != null) {
106            return value;
107        }
108
109        for (ToIntFunction<String> function : filters) {
110            int ordinal = function.applyAsInt(path);
111
112            if (ordinal != Integer.MAX_VALUE) {
113                return ordinal;
114            }
115        }
116
117        return Integer.MAX_VALUE;
118    }
119
120    private static int compare(SortWrapper wrapper1, SortWrapper wrapper2) {
121        int compare = wrapper1.getOrdinal() - wrapper2.getOrdinal();
122
123        if (compare != 0) {
124            return compare;
125        }
126
127        return wrapper1.getPath().compareTo(wrapper2.getPath());
128    }
129
130    @Override
131    public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
132        in.entries()
133                .filter(resource -> resource.type()
134                        .equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE))
135                .map((resource) -> new SortWrapper(resource, getOrdinal(resource)))
136                .sorted(OrderResourcesPlugin::compare)
137                .forEach((wrapper) -> out.add(wrapper.getResource()));
138        in.entries()
139                .filter(other -> !other.type()
140                        .equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE))
141                .forEach((other) -> out.add(other));
142
143        return out.build();
144    }
145
146    @Override
147    public Category getType() {
148        return Category.SORTER;
149    }
150
151    @Override
152    public String getDescription() {
153        return PluginsResourceBundle.getDescription(NAME);
154    }
155
156    @Override
157    public boolean hasArguments() {
158        return true;
159    }
160
161    @Override
162    public String getArgumentsDescription() {
163       return PluginsResourceBundle.getArgument(NAME);
164    }
165
166    @Override
167    public void configure(Map<String, String> config) {
168        List<String> patterns = Utils.parseList(config.get(NAME));
169        int ordinal = 0;
170
171        for (String pattern : patterns) {
172            if (pattern.startsWith("@")) {
173                File file = new File(pattern.substring(1));
174
175                if (file.exists()) {
176                    List<String> lines;
177
178                    try {
179                        lines = Files.readAllLines(file.toPath());
180                    } catch (IOException ex) {
181                        throw new UncheckedIOException(ex);
182                    }
183
184                    for (String line : lines) {
185                        if (!line.startsWith("#")) {
186                            orderedPaths.put(line + ".class", ordinal++);
187                        }
188                    }
189                }
190            } else {
191                final int result = ordinal++;
192                final PathMatcher matcher = Utils.getPathMatcher(JRT_FILE_SYSTEM, pattern);
193                ToIntFunction<String> function = (path)-> matcher.matches(JRT_FILE_SYSTEM.getPath(path)) ? result : Integer.MAX_VALUE;
194                filters.add(function);
195             }
196        }
197    }
198}
199