001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.q2; 020 021import org.jline.reader.Candidate; 022import org.jline.reader.Completer; 023import org.jline.reader.LineReader; 024import org.jline.reader.ParsedLine; 025import org.jline.utils.AttributedString; 026 027import java.io.File; 028import java.io.IOException; 029import java.net.JarURLConnection; 030import java.net.URI; 031import java.net.URISyntaxException; 032import java.net.URL; 033import java.util.*; 034import java.util.jar.JarEntry; 035 036public class CLIPrefixedClassNameCompleter implements Completer { 037 protected final Collection<Candidate> candidates = new ArrayList<>(); 038 039 public CLIPrefixedClassNameCompleter(Collection<String> prefixes) throws IOException { 040 for (String s : getClassNames(prefixes)) { 041 candidates.add(new Candidate(AttributedString.stripAnsi(s), s, null, null, null, null, true)); 042 } 043 } 044 045 private static String[] getClassNames(Collection<String> prefixes) throws IOException { 046 Set<String> classes = new HashSet<>(); 047 for (String prefix : prefixes) { 048 classes.addAll(getClassEntries(prefix)); 049 } 050 Set<String> classNames = new TreeSet<String>(); 051 for (String name : classes) { 052 if (name.endsWith(".class")) { 053 classNames.add(name.replace('/', '.').substring(0, name.length() - 6)); 054 } 055 } 056 return classNames.toArray(new String[classNames.size()]); 057 } 058 059 private static List<String> getClassEntries(String prefix) throws IOException { 060 final String p = prefix.replaceAll("\\.", "\\/"); 061 List<String> result = new ArrayList<String>(); 062 063 Enumeration<URL> urls = CLIPrefixedClassNameCompleter.class.getClassLoader().getResources(p); 064 while (urls.hasMoreElements()) { 065 URL url = urls.nextElement(); 066 if (url == null) { 067 return Collections.emptyList(); 068 } 069 070 try { 071 final List<String> lst = url.getProtocol().equals("jar") ? 072 resolveModuleEntriesFromJar(url, p) : 073 resolveModuleEntriesFromFiles(url, p); 074 result.addAll(lst); 075 } catch (URISyntaxException e) { 076 throw new IOException("Bad URL", e); 077 } 078 } 079 return result; 080 } 081 082 private static List<String> resolveModuleEntriesFromFiles(URL url, String _prefix) throws IOException, URISyntaxException { 083 final String prefix = _prefix.endsWith("/") ? _prefix : _prefix + "/"; 084 List<String> resourceList = new ArrayList<String>(); 085 final URI uri = url.toURI(); 086 File f = new File(uri); 087 addFiles(f, prefix, resourceList); 088 return resourceList; 089 } 090 091 private static void addFiles(File f, String prefix, List<String> resourceList) { 092 File files[] = f.listFiles(); 093 if (files == null) { 094 return; 095 } 096 for (File file : files) { 097 if (file.isDirectory()) { 098 addFiles(file, prefix + file.getName() + System.getProperty("file.separator"), resourceList); 099 } else { 100 resourceList.add(file.getName()); 101 } 102 } 103 } 104 105 private static List<String> resolveModuleEntriesFromJar(URL url, String _prefix) throws IOException { 106 final String prefix = _prefix.endsWith("/") ? _prefix : _prefix + "/"; 107 List<String> resourceList = new ArrayList<String>(); 108 JarURLConnection conn = (JarURLConnection) url.openConnection(); 109 Enumeration entries = conn.getJarFile().entries(); 110 while (entries.hasMoreElements()) { 111 JarEntry entry = (JarEntry) entries.nextElement(); 112 String name = entry.getName(); 113 if (name.startsWith(prefix) && !name.contains("$") && !entry.isDirectory()) { 114 name = name.substring(prefix.length()).toLowerCase(); 115 if (!name.contains("/")) { 116 resourceList.add(name); 117 } 118 } 119 } 120 return resourceList; 121 } 122 123 @Override 124 public void complete(LineReader reader, ParsedLine line, List<Candidate> cand) { 125 if (line.wordIndex() == 0 && cand != null) 126 cand.addAll(this.candidates); 127 } 128 129 public String toString () { 130 StringBuilder sb = new StringBuilder ("CLIPrefixedClassNameCompletor["); 131 sb.append(hashCode()); 132 for (Candidate c : candidates) { 133 sb.append (System.getProperty("line.separator")); 134 sb.append (" "); 135 sb.append (c.value()); 136 } 137 sb.append(']'); 138 return sb.toString(); 139 } 140}