Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove ini4j && improve ini config file parsing #949

Merged
merged 1 commit into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading