Skip to content

Commit

Permalink
refactor: remove ini4j && improve ini config file parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
yndu13 committed Sep 27, 2024
1 parent 9b3cc8d commit 4730adb
Show file tree
Hide file tree
Showing 6 changed files with 385 additions and 20 deletions.
1 change: 1 addition & 0 deletions aliyun-java-sdk-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<version>0.5.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@

import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.utils.AuthUtils;
import com.aliyuncs.utils.ProfileUtils;
import com.aliyuncs.utils.StringUtils;
import org.ini4j.Profile;
import org.ini4j.Wini;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ProfileCredentialsProvider implements AlibabaCloudCredentialsProvider {
private static volatile Wini ini;
private static volatile Map<String, Map<String, String>> ini;

private static Wini getIni(String filePath) throws IOException {
private static Map<String, Map<String, String>> getIni(String filePath) throws IOException {
if (null == ini) {
synchronized (ProfileCredentialsProvider.class) {
if (null == ini) {
ini = new Wini(new File(filePath));
ini = ProfileUtils.parseFile(filePath);
}
}
}
Expand All @@ -34,7 +31,7 @@ public AlibabaCloudCredentials getCredentials() throws ClientException {
if (filePath.isEmpty()) {
throw new ClientException("The specified credentials file is empty");
}
Wini ini;
Map<String, Map<String, String>> ini;
try {
ini = getIni(filePath);
} catch (IOException e) {
Expand All @@ -49,16 +46,14 @@ public AlibabaCloudCredentials getCredentials() throws ClientException {
return createCredential(clientConfig, credentialsProviderFactory);
}

private Map<String, Map<String, String>> loadIni(Wini ini) {
private Map<String, Map<String, String>> loadIni(Map<String, Map<String, String>> ini) {
Map<String, Map<String, String>> client = new HashMap<String, Map<String, String>>();
boolean enable;
for (Map.Entry<String, Profile.Section> clientType : ini.entrySet()) {
enable = clientType.getValue().get(AuthConstant.INI_ENABLE, boolean.class);
if (enable) {
String enable;
for (Map.Entry<String, Map<String, String>> clientType : ini.entrySet()) {
enable = clientType.getValue().get(AuthConstant.INI_ENABLE);
if (Boolean.parseBoolean(enable)) {
Map<String, String> clientConfig = new HashMap<String, String>();
for (Map.Entry<String, String> enabledClient : clientType.getValue().entrySet()) {
clientConfig.put(enabledClient.getKey(), enabledClient.getValue());
}
clientConfig.putAll(clientType.getValue());
client.put(clientType.getKey(), clientConfig);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package com.aliyuncs.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
* Only for internal use within the package, do not use it arbitrarily, backward compatibility and sustainability cannot be guaranteed.
*/
public class ProfileUtils {
private final static Log log = LogFactory.getLog(ProfileUtils.class);
private static final Pattern EMPTY_LINE = Pattern.compile("^[\t ]*$");

public static Map<String, Map<String, String>> parseFile(String profilePath) throws IOException {
return parseFile(new FileReader(profilePath));
}

static Map<String, Map<String, String>> parseFile(Reader input) throws IOException {
ParserProgress progress = new ParserProgress();
BufferedReader profileReader = null;
try {
profileReader = new BufferedReader(input);
String line;
while ((line = profileReader.readLine()) != null) {
parseLine(progress, line);
}
} finally {
if (profileReader != null) {
profileReader.close();
}
}
return progress.profiles;
}

private static void parseLine(ParserProgress progress, String line) {
++progress.currentLineNumber;
if (!EMPTY_LINE.matcher(line).matches() && !(line.startsWith("#") || line.startsWith(";"))) {
if (isSectionDefinitionLine(line)) {
readSectionDefinitionLine(progress, line);
} else if (line.startsWith("\t")) {
readPropertyContinuationLine(progress, line);
} else {
readPropertyDefinitionLine(progress, line);
}
}
}

private static void readSectionDefinitionLine(ParserProgress progress, String line) {
String lineWithoutComments = removeTrailingComments(line, "#", ";");
String lineWithoutWhitespace = lineWithoutComments.trim();

if (!lineWithoutWhitespace.endsWith("]")) {
throw new IllegalArgumentException(String.format("Section definition must end with ']' on line %s: %s", progress.currentLineNumber, line));
}

String lineWithoutBrackets = lineWithoutWhitespace.substring(1, lineWithoutWhitespace.length() - 1);
String profileName = lineWithoutBrackets.trim();
if (profileName.isEmpty()) {
progress.ignoringCurrentProfile = true;
return;
}
progress.currentProfileBeingRead = profileName;
progress.currentPropertyBeingRead = null;
progress.ignoringCurrentProfile = false;
if (!progress.profiles.containsKey(profileName)) {
progress.profiles.put(profileName, new LinkedHashMap<String, String>());
}
}

private static void readPropertyDefinitionLine(ParserProgress progress, String line) {
// Invalid profile, ignore its properties
if (progress.ignoringCurrentProfile) {
return;
}
if (progress.currentProfileBeingRead == null) {
// throw new IllegalArgumentException(String.format("Expected a profile definition on line %s", progress.currentLineNumber));
// To be consistent with ini4j's behavior
progress.currentProfileBeingRead = "?";
if (!progress.profiles.containsKey(progress.currentProfileBeingRead)) {
progress.profiles.put(progress.currentProfileBeingRead, new LinkedHashMap<String, String>());
}
}

// Comments with property must have whitespace before them, or they will be considered part of the value
String lineWithoutComments = removeTrailingComments(line, " #", " ;", "\t#", "\t;");
String lineWithoutWhitespace = lineWithoutComments.trim();
Property<String, String> property = parsePropertyDefinition(progress, lineWithoutWhitespace);

if (progress.profiles.get(progress.currentProfileBeingRead).containsKey(property.key())) {
log.warn("Duplicate property '" + property.key() + "' detected on line " + progress.currentLineNumber +
". The later one in the file will be used.");
}

progress.currentPropertyBeingRead = property.key();

progress.profiles.get(progress.currentProfileBeingRead).put(property.key(), property.value());
}

private static void readPropertyContinuationLine(ParserProgress progress, String line) {
// Invalid profile, ignore its properties
if (progress.ignoringCurrentProfile) {
return;
}
if (progress.currentProfileBeingRead == null) {
// throw new IllegalArgumentException(String.format("Expected a profile definition on line %s", progress.currentLineNumber));
// To be consistent with ini4j's behavior
progress.currentProfileBeingRead = "?";
if (!progress.profiles.containsKey(progress.currentProfileBeingRead)) {
progress.profiles.put(progress.currentProfileBeingRead, new LinkedHashMap<String, String>());
}
}

// Comments are not removed on property continuation lines. They're considered part of the value.
line = line.trim();
Map<String, String> profileProperties = progress.profiles.get(progress.currentProfileBeingRead);

String currentPropertyValue = profileProperties.get(progress.currentPropertyBeingRead);
String newPropertyValue = currentPropertyValue + "\n" + line;
profileProperties.put(progress.currentPropertyBeingRead, newPropertyValue);
}

private static Property<String, String> parsePropertyDefinition(ParserProgress progress, String line) {
int firstEqualsLocation = line.indexOf('=');
String propertyKey = null;
String propertyValue = null;
if (firstEqualsLocation == -1) {
// throw new IllegalArgumentException(String.format("Expected an '=' sign defining a property on line %s", progress.currentLineNumber));
// To be consistent with ini4j's behavior
propertyKey = line.trim();
} else {
propertyKey = line.substring(0, firstEqualsLocation).trim();
propertyValue = line.substring(firstEqualsLocation + 1).trim();
}

if (propertyKey.isEmpty()) {
throw new IllegalArgumentException(String.format("Property did not have a name on line %s", progress.currentLineNumber));
}

return new Property<String, String>(propertyKey, propertyValue);
}

private static boolean isSectionDefinitionLine(String line) {
return line.trim().startsWith("[");
}

private static String removeTrailingComments(String line, String... commentPatterns) {
int earliestMatchIndex = line.length();
for (String pattern : commentPatterns) {
int index = line.indexOf(pattern);
if (index >= 0 && index < earliestMatchIndex) {
earliestMatchIndex = index;
}
}
return line.substring(0, earliestMatchIndex);
}

private static final class ParserProgress {
private int currentLineNumber;
private String currentProfileBeingRead;
private String currentPropertyBeingRead;
private boolean ignoringCurrentProfile;
private final Map<String, Map<String, String>> profiles;

private ParserProgress() {
this.currentLineNumber = 0;
this.currentProfileBeingRead = null;
this.currentPropertyBeingRead = null;
this.ignoringCurrentProfile = false;
this.profiles = new LinkedHashMap<String, Map<String, String>>();
}
}

private static final class Property<Key, Value> {
private final Key key;
private final Value value;

private Property(Key key, Value value) {
this.key = key;
this.value = value;
}

public Key key() {
return this.key;
}

public Value value() {
return this.value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.utils.AuthUtils;
import org.ini4j.Wini;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
Expand Down Expand Up @@ -200,8 +199,8 @@ public void getIniTest() throws NoSuchMethodException, InvocationTargetException
getIni.setAccessible(true);
String file = ProfileCredentialsProviderTest.class.getClassLoader().
getResource("configTest.ini").getPath();
Wini firstIni = (Wini) getIni.invoke(profileCredentialsProvider, file);
Wini secondIni = (Wini) getIni.invoke(profileCredentialsProvider, file);
Map<String, Map<String, String>> firstIni = (Map<String, Map<String, String>>) getIni.invoke(profileCredentialsProvider, file);
Map<String, Map<String, String>> secondIni = (Map<String, Map<String, String>>) getIni.invoke(profileCredentialsProvider, file);
Assert.assertTrue(firstIni.equals(secondIni));
}
}
Loading

0 comments on commit 4730adb

Please sign in to comment.