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 b54a0f8 commit 83548d2
Show file tree
Hide file tree
Showing 7 changed files with 283 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
Expand Up @@ -119,6 +119,7 @@ public ISigner getSigner() {
}

@Override
@Deprecated
public void setCredentialsProvider(AlibabaCloudCredentialsProvider credentialsProvider) {
if (credential != null) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
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;

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(" ") || 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));
}

// 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));
}

// 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('=');
if (firstEqualsLocation == -1) {
throw new IllegalArgumentException(String.format("Expected an '=' sign defining a property on line %s", progress.currentLineNumber));
}

String propertyKey = line.substring(0, firstEqualsLocation).trim();
String 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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.aliyuncs.utils;

import org.ini4j.Wini;
import org.junit.Assert;
import org.junit.Test;

import java.io.IOException;
import java.io.StringReader;
import java.util.Map;

public class ProfileUtilsTest {
@Test
public void testProfile() throws IOException {
String context = "[profile1]\n"
+ ";comment\n"
+ "#comment\n"
+ "enable = false #comment\n"
+ "[profile2]\n"
+ "region = cn-hangzhou#comment\n"
+ "[default]\n"
+ "default_property = property1 \t\n\n"
+ "[default]\n"
+ "default_property = property2\n"
+ "[profile3]\n"
+ "int = 1\n"
+ " int = 2\n"
+ "int = \n"
+ "\t3\n"
+ "str = #comment\n"
+ "\ttest\n";
Wini ini = new Wini(new StringReader(context));
Map<String, Map<String, String>> iniMap = ProfileUtils.parseFile(new StringReader(context));
Assert.assertEquals(4, ini.size());
Assert.assertEquals(4, iniMap.size());
Assert.assertEquals("false #comment", ini.get("profile1").get("enable"));
Assert.assertEquals("false", iniMap.get("profile1").get("enable"));
Assert.assertEquals(ini.get("profile2").get("region"), iniMap.get("profile2").get("region"));
Assert.assertEquals(ini.get("default").get("default_property"), iniMap.get("default").get("default_property"));
Assert.assertEquals("\n3", iniMap.get("profile3").get("int"));
Assert.assertEquals("\ntest", iniMap.get("profile3").get("str"));

context = "[profile1]\n"
+ "enable = false\n"
+ "[profile2]\n"
+ "enable = true\n"
+ "[profile3]\n"
+ "enable = null\n"
+ "[profile4]\n"
+ "enable = 1\n"
+ "[profile5]\n"
+ "enable = False\n"
+ "[profile6]\n"
+ "enable = True\n"
+ "[profile7]\n"
+ "enable =\n";
ini = new Wini(new StringReader(context));
iniMap = ProfileUtils.parseFile(new StringReader(context));
Assert.assertEquals(7, ini.size());
Assert.assertEquals(7, iniMap.size());
Assert.assertEquals(false, ini.get("profile1").get("enable", Boolean.class));
Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile1").get("enable")));
Assert.assertEquals(true, ini.get("profile2").get("enable", Boolean.class));
Assert.assertEquals(true, Boolean.parseBoolean(iniMap.get("profile2").get("enable")));
Assert.assertEquals(false, ini.get("profile3").get("enable", Boolean.class));
Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile3").get("enable")));
Assert.assertEquals(false, ini.get("profile4").get("enable", Boolean.class));
Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile4").get("enable")));
Assert.assertEquals(false, ini.get("profile5").get("enable", Boolean.class));
Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile5").get("enable")));
Assert.assertEquals(true, ini.get("profile6").get("enable", Boolean.class));
Assert.assertEquals(true, Boolean.parseBoolean(iniMap.get("profile6").get("enable")));
Assert.assertEquals(false, ini.get("profile7").get("enable", Boolean.class));
Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile7").get("enable")));
}

}
16 changes: 15 additions & 1 deletion aliyun-java-sdk-core/src/test/resources/configTest.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
[default]
[ ] ;invalid
enable = false ;comments
enable = true #comments
type = access_key
access_key_id = foo

[client] ;comments
enable = false ;comments
enable = true #comments
type = access_key # type
access_key_id = foo # access_key_id
access_key_secret = bar # access_key_secret


[default]
enable = true
type = access_key
access_key_id = foo
Expand Down

0 comments on commit 83548d2

Please sign in to comment.