diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java index 4daeaf5d..50159b52 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java @@ -6,6 +6,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -37,6 +38,7 @@ import org.apache.commons.io.FileUtils; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.awt.Graphics; import java.awt.image.BufferedImage; @@ -51,16 +53,18 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nonnull; public final class HDSkinManager implements IResourceManagerReloadListener { + private static final Logger logger = LogManager.getLogger(); + public static final ExecutorService skinUploadExecutor = Executors.newSingleThreadExecutor(); public static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8); public static final CloseableHttpClient httpClient = HttpClients.createSystem(); @@ -89,7 +93,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener { private SkinResourceManager resources = new SkinResourceManager(); // private ExecutorService executor = Executors.newCachedThreadPool(); - private Class skinsClass = null; + private Function, GuiSkins> skinsGuiFunc = GuiSkins::new; private HDSkinManager() { @@ -99,20 +103,13 @@ public final class HDSkinManager implements IResourceManagerReloadListener { addSkinServerType(BethlehemSkinServer.class); } - public void setPrefferedSkinsGuiClass(Class clazz) { - skinsClass = clazz; + public void setSkinsGui(Function, GuiSkins> skinsGuiFunc) { + Preconditions.checkNotNull(skinsGuiFunc, "skinsGuiFunc"); + this.skinsGuiFunc = skinsGuiFunc; } public GuiSkins createSkinsGui() { - if (skinsClass != null) { - try { - return skinsClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - e.printStackTrace(); - } - } - - return new GuiSkins(); + return skinsGuiFunc.apply(ImmutableList.copyOf(this.skinServers)); } public Optional getSkinLocation(GameProfile profile1, final Type type, boolean loadIfAbsent) { @@ -219,7 +216,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener { break; } } catch (IOException e) { - LogManager.getLogger().trace(e); + logger.trace(e); } } @@ -254,19 +251,10 @@ public final class HDSkinManager implements IResourceManagerReloadListener { this.skinServers.add(skinServer); } - @Deprecated - public SkinServer getGatewayServer() { - return this.skinServers.get(0); - } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public static CompletableFuture getPreviewTextureManager(GameProfile profile) { - return INSTANCE.getGatewayServer().getPreviewTextures(profile).thenApply(PreviewTextureManager::new); - } - public void addClearListener(ISkinCacheClearListener listener) { clearListeners.add(listener); } @@ -296,8 +284,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener { try { return callback.onSkinCacheCleared(); } catch (Exception e) { - LiteLoaderLogger.warning("Exception ancountered calling skin listener '{}'. It will be removed.", callback.getClass().getName()); - e.printStackTrace(); + logger.warn("Exception encountered calling skin listener '{}'. It will be removed.", callback.getClass().getName(), e); return false; } } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/LocalTexture.java b/src/hdskins/java/com/voxelmodpack/hdskins/LocalTexture.java index 6b2ee1a7..e843d46f 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/LocalTexture.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/LocalTexture.java @@ -1,20 +1,18 @@ package com.voxelmodpack.hdskins; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; - -import javax.imageio.ImageIO; - import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; - import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.resources.SkinManager.SkinAvailableCallback; import net.minecraft.util.ResourceLocation; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; + public class LocalTexture { private final TextureManager textureManager = Minecraft.getMinecraft().getTextureManager(); @@ -33,7 +31,7 @@ public class LocalTexture { this.blank = blank; this.type = type; - String file = type.name().toLowerCase() + "s/preview_${profile.getName()}.png"; + String file = String.format("%s/preview_%s.png", type.name().toLowerCase(), profile.getName()); remoteResource = new ResourceLocation(file); textureManager.deleteTexture(remoteResource); @@ -113,6 +111,7 @@ public class LocalTexture { } public interface IBlankSkinSupplier { + ResourceLocation getBlankSkin(Type type); } } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/PreviewTextureManager.java b/src/hdskins/java/com/voxelmodpack/hdskins/PreviewTextureManager.java index 18cf587d..d3a6462a 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/PreviewTextureManager.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/PreviewTextureManager.java @@ -1,14 +1,17 @@ package com.voxelmodpack.hdskins; import com.google.common.collect.Maps; +import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; +import com.voxelmodpack.hdskins.skins.SkinServer; import net.minecraft.client.renderer.IImageBuffer; import net.minecraft.client.resources.SkinManager; import net.minecraft.util.ResourceLocation; import java.awt.image.BufferedImage; import java.util.Map; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nullable; @@ -20,7 +23,7 @@ public class PreviewTextureManager { private final Map textures; - PreviewTextureManager(MinecraftTexturesPayload payload) { + private PreviewTextureManager(MinecraftTexturesPayload payload) { this.textures = payload.getTextures(); } @@ -52,4 +55,8 @@ public class PreviewTextureManager { return skinTexture; } + + public static CompletableFuture load(SkinServer server, GameProfile profile) { + return server.getPreviewTextures(profile).thenApply(PreviewTextureManager::new); + } } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/gui/EntityPlayerModel.java b/src/hdskins/java/com/voxelmodpack/hdskins/gui/EntityPlayerModel.java index ede55f4f..9a9e45e9 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/gui/EntityPlayerModel.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/gui/EntityPlayerModel.java @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; -import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.LocalTexture; import com.voxelmodpack.hdskins.LocalTexture.IBlankSkinSupplier; import net.minecraft.client.Minecraft; @@ -35,12 +34,15 @@ public class EntityPlayerModel extends EntityLivingBase implements IBlankSkinSup protected final LocalTexture skin; protected final LocalTexture elytra; - public final GameProfile profile; + + private final GameProfile profile; + private final GuiSkins skins; protected boolean previewThinArms = false; - public EntityPlayerModel(GameProfile gameprofile) { + public EntityPlayerModel(GuiSkins skins, GameProfile gameprofile) { super(new DummyWorld()); + this.skins = skins; profile = gameprofile; skin = new LocalTexture(profile, Type.SKIN, this); @@ -48,10 +50,10 @@ public class EntityPlayerModel extends EntityLivingBase implements IBlankSkinSup } public void reloadRemoteSkin(SkinManager.SkinAvailableCallback listener) { - HDSkinManager.getPreviewTextureManager(profile).thenAccept(ptm -> { + this.skins.loadTextures(profile).thenAcceptAsync(ptm -> { skin.setRemote(ptm, listener); elytra.setRemote(ptm, listener); - }); + }, Minecraft.getMinecraft()::addScheduledTask); // run on main thread } public void setLocalTexture(File skinTextureFile, Type type) { diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/gui/GuiSkins.java b/src/hdskins/java/com/voxelmodpack/hdskins/gui/GuiSkins.java index 5a98ef9e..c99065e1 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/gui/GuiSkins.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/gui/GuiSkins.java @@ -1,12 +1,9 @@ package com.voxelmodpack.hdskins.gui; -import static com.mojang.authlib.minecraft.MinecraftProfileTexture.Type.ELYTRA; -import static com.mojang.authlib.minecraft.MinecraftProfileTexture.Type.SKIN; -import static net.minecraft.client.renderer.GlStateManager.*; - import com.google.common.base.Splitter; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.minelittlepony.gui.Button; import com.minelittlepony.gui.GameGui; import com.minelittlepony.gui.IconicButton; @@ -16,10 +13,11 @@ import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.mumfrey.liteloader.util.log.LiteLoaderLogger; import com.voxelmodpack.hdskins.HDSkinManager; +import com.voxelmodpack.hdskins.PreviewTextureManager; +import com.voxelmodpack.hdskins.skins.SkinServer; import com.voxelmodpack.hdskins.skins.SkinUpload; import com.voxelmodpack.hdskins.skins.SkinUploadResponse; import com.voxelmodpack.hdskins.upload.awt.ThreadOpenFilePNG; - import net.minecraft.client.Minecraft; import net.minecraft.client.audio.PositionedSoundRecord; import net.minecraft.client.gui.Gui; @@ -34,7 +32,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.EnumHand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; - import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.LogManager; import org.lwjgl.BufferUtils; @@ -46,17 +43,27 @@ import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.DoubleBuffer; +import java.util.Iterator; +import java.util.List; import java.util.Map; - +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; import javax.annotation.Nullable; import javax.imageio.ImageIO; import javax.swing.UIManager; +import static com.mojang.authlib.minecraft.MinecraftProfileTexture.Type.ELYTRA; +import static com.mojang.authlib.minecraft.MinecraftProfileTexture.Type.SKIN; +import static net.minecraft.client.renderer.GlStateManager.*; + public class GuiSkins extends GameGui { private static final int MAX_SKIN_DIMENSION = 1024; private int updateCounter = 0; + private final Iterator skinServers; + private SkinServer gateway; + private Button btnUpload; private Button btnClear; @@ -93,11 +100,9 @@ public class GuiSkins extends GameGui { private File pendingSkinFile; private File selectedSkin; - - private int lastMouseX = 0; - private static GuiSkins instance; + private GuiSkins instance; protected CubeMap panorama; @@ -112,9 +117,17 @@ public class GuiSkins extends GameGui { } } - public GuiSkins() { + public GuiSkins(List servers) { instance = this; + // Generate a cycled iterator that will never run out of entries. + this.skinServers = cycle(servers, SkinServer::verifyGateway); + if (this.skinServers.hasNext()) { + this.gateway = this.skinServers.next(); + } else { + this.uploadError = "There are no valid skin servers available! Check your config."; + } + Minecraft minecraft = Minecraft.getMinecraft(); GameProfile profile = minecraft.getSession().getProfile(); @@ -126,20 +139,25 @@ public class GuiSkins extends GameGui { rm.options = minecraft.gameSettings; rm.renderViewEntity = localPlayer; - reloadRemoteSkin(); - - fetchingSkin = true; + if (gateway != null) { + reloadRemoteSkin(); + fetchingSkin = true; + } panorama = new CubeMap(this); initPanorama(); } + private static Iterator cycle(List list, Predicate filter) { + return Iterables.cycle(Iterables.filter(list, filter::test)).iterator(); + } + protected void initPanorama() { panorama.setSource("hdskins:textures/cubemaps/cubemap0_%d.png"); } protected EntityPlayerModel getModel(GameProfile profile) { - return new EntityPlayerModel(profile); + return new EntityPlayerModel(this, profile); } @Override @@ -207,7 +225,7 @@ public class GuiSkins extends GameGui { @Override public void initGui() { GLWindow.current().setDropTargetListener(files -> { - files.stream().findFirst().ifPresent(instance::loadLocalFile); + files.stream().findFirst().ifPresent(this::loadLocalFile); }); panorama.init(); @@ -216,7 +234,7 @@ public class GuiSkins extends GameGui { addButton(new Label(34, 34, "hdskins.local", 0xffffff)); addButton(new Label(width / 2 + 34, 34, "hdskins.server", 0xffffff)); - addButton(new Button(width / 2 - 150, height - 27, 90, 20, "hdskins.options.browse", sender ->{ + addButton(new Button(width / 2 - 150, height - 27, 90, 20, "hdskins.options.browse", sender -> { selectedSkin = null; localPlayer.releaseTextures(); openFileThread = new ThreadOpenFilePNG(mc, format("hdskins.open.title"), (fileDialog, dialogResult) -> { @@ -262,9 +280,18 @@ public class GuiSkins extends GameGui { }).setIcon(new ItemStack(Items.ELYTRA))).setEnabled(textureType == SKIN).setTooltip(format("hdskins.mode.skin", toTitleCase(ELYTRA.name()))); - addButton(new Button(width - 25, height - 65, 20, 20, "?", sender -> { - mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.ENTITY_VILLAGER_YES, 1)); - })).setTooltip(Splitter.on("\r\n").splitToList(HDSkinManager.INSTANCE.getGatewayServer().toString())); + addButton(new Button(width - 25, height - 65, 20, 20, "?", this::switchServer)) + .setTooltip(Splitter.on("\r\n").splitToList(gateway == null ? "" : gateway.toString())); + } + + private void switchServer(Button sender) { + mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.ENTITY_VILLAGER_YES, 1)); + gateway = skinServers.next(); + if (gateway != null) { + sender.setTooltip(Splitter.on("\r\n").splitToList(gateway.toString())); + reloadRemoteSkin(); + fetchingSkin = true; + } } @Override @@ -346,6 +373,7 @@ public class GuiSkins extends GameGui { @Override protected void actionPerformed(GuiButton guiButton) { + if (openFileThread == null && !uploadingSkin && clearMessage()) { super.actionPerformed(guiButton); } @@ -353,6 +381,10 @@ public class GuiSkins extends GameGui { @Override protected void mouseClicked(int mouseX, int mouseY, int button) throws IOException { + if (this.gateway == null) { + // doing things might break everything if there is no gateway + return; + } if (clearMessage()) { super.mouseClicked(mouseX, mouseY, button); @@ -436,7 +468,7 @@ public class GuiSkins extends GameGui { if (!localPlayer.isUsingLocalTexture()) { Gui.drawRect(40, height / 2 - 12, width / 2 - 40, height / 2 + 12, 0xB0000000); - drawCenteredString(fontRenderer, localMessage, (int)xPos1, height / 2 - 4, 0xffffff); + drawCenteredString(fontRenderer, localMessage, (int) xPos1, height / 2 - 4, 0xffffff); } if (fetchingSkin) { @@ -446,10 +478,10 @@ public class GuiSkins extends GameGui { Gui.drawRect((int) (xPos2 - width / 4 + 40), height / 2 - lineHeight, width - 40, height / 2 + lineHeight, 0xB0000000); if (throttledByMojang) { - drawCenteredString(fontRenderer, format("hdskins.error.mojang"), (int)xPos2, height / 2 - 10, 0xffffff); - drawCenteredString(fontRenderer, format("hdskins.error.mojang.wait"), (int)xPos2, height / 2 + 2, 0xffffff); + drawCenteredString(fontRenderer, format("hdskins.error.mojang"), (int) xPos2, height / 2 - 10, 0xffffff); + drawCenteredString(fontRenderer, format("hdskins.error.mojang.wait"), (int) xPos2, height / 2 + 2, 0xffffff); } else { - drawCenteredString(fontRenderer, format("hdskins.fetch"), (int)xPos2, height / 2 - 4, 0xffffff); + drawCenteredString(fontRenderer, format("hdskins.fetch"), (int) xPos2, height / 2 - 4, 0xffffff); } } @@ -496,8 +528,8 @@ public class GuiSkins extends GameGui { rotate(((updateCounter + partialTick) * 2.5F) % 360, 0, 1, 0); - thePlayer.rotationYawHead = (float)Math.atan(mouseX / 20) * 30; - thePlayer.rotationPitch = (float)Math.atan(mouseY / 40) * -20; + thePlayer.rotationYawHead = (float) Math.atan(mouseX / 20) * 30; + thePlayer.rotationPitch = (float) Math.atan(mouseY / 40) * -20; mc.getRenderManager().renderEntity(thePlayer, 0, 0, 0, 0, 1, false); @@ -543,8 +575,7 @@ public class GuiSkins extends GameGui { uploadMessage = format(uploadMsg); btnUpload.enabled = canUpload(); - HDSkinManager.INSTANCE.getGatewayServer() - .uploadSkin(mc.getSession(), new SkinUpload(textureType, path, getMetadata())) + gateway.uploadSkin(mc.getSession(), new SkinUpload(textureType, path, getMetadata())) .thenAccept(this::onUploadComplete) .exceptionally(this::onUploadFailure); } @@ -574,4 +605,9 @@ public class GuiSkins extends GameGui { protected boolean canUpload() { return selectedSkin != null && !uploadingSkin && !pendingRemoteSkinRefresh; } + + CompletableFuture loadTextures(GameProfile profile) { + return PreviewTextureManager.load(this.gateway, profile); + } + } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/LegacySkinServer.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/LegacySkinServer.java index 9ea4318b..d1193201 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/skins/LegacySkinServer.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/LegacySkinServer.java @@ -137,6 +137,11 @@ public class LegacySkinServer implements SkinServer { return String.format("%s/%s/%s.png", address, path, uuid); } + @Override + public boolean verifyGateway() { + return !Strings.isNullOrEmpty(this.gateway); + } + @Override public String toString() { return new IndentedToStringStyle.Builder(this) diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinServer.java b/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinServer.java index 48f55b51..c0ce408d 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinServer.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/skins/SkinServer.java @@ -36,6 +36,10 @@ public interface SkinServer extends Exposable { return CallableFutures.asyncFailableFuture(() -> loadProfileData(profile), HDSkinManager.skinDownloadExecutor); } + default boolean verifyGateway() { + return true; + } + static void verifyServerConnection(Session session, String serverId) throws AuthenticationException { MinecraftSessionService service = Minecraft.getMinecraft().getSessionService(); service.joinServer(session.getProfile(), session.getToken(), serverId); diff --git a/src/main/java/com/minelittlepony/MineLittlePony.java b/src/main/java/com/minelittlepony/MineLittlePony.java index 5c9e3594..3cd4e78a 100644 --- a/src/main/java/com/minelittlepony/MineLittlePony.java +++ b/src/main/java/com/minelittlepony/MineLittlePony.java @@ -77,7 +77,7 @@ public class MineLittlePony { // logger.info("Set MineLP skin server URL."); manager.addClearListener(ponyManager); - manager.setPrefferedSkinsGuiClass(GuiSkinsMineLP.class); + manager.setSkinsGui(GuiSkinsMineLP::new); RenderManager rm = minecraft.getRenderManager(); renderManager.initialisePlayerRenderers(rm); diff --git a/src/main/java/com/minelittlepony/hdskins/gui/EntityPonyModel.java b/src/main/java/com/minelittlepony/hdskins/gui/EntityPonyModel.java index 843fdc7c..211bbe41 100644 --- a/src/main/java/com/minelittlepony/hdskins/gui/EntityPonyModel.java +++ b/src/main/java/com/minelittlepony/hdskins/gui/EntityPonyModel.java @@ -3,7 +3,7 @@ package com.minelittlepony.hdskins.gui; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.voxelmodpack.hdskins.gui.EntityPlayerModel; - +import com.voxelmodpack.hdskins.gui.GuiSkins; import net.minecraft.util.ResourceLocation; /** @@ -16,8 +16,8 @@ public class EntityPonyModel extends EntityPlayerModel { public boolean wet = false; - public EntityPonyModel(GameProfile profile) { - super(profile); + public EntityPonyModel(GuiSkins skins, GameProfile profile) { + super(skins, profile); } @Override diff --git a/src/main/java/com/minelittlepony/hdskins/gui/GuiSkinsMineLP.java b/src/main/java/com/minelittlepony/hdskins/gui/GuiSkinsMineLP.java index 2f3afad7..ceb262d3 100644 --- a/src/main/java/com/minelittlepony/hdskins/gui/GuiSkinsMineLP.java +++ b/src/main/java/com/minelittlepony/hdskins/gui/GuiSkinsMineLP.java @@ -9,13 +9,15 @@ import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.voxelmodpack.hdskins.gui.EntityPlayerModel; import com.voxelmodpack.hdskins.gui.GuiSkins; - +import com.voxelmodpack.hdskins.skins.SkinServer; import net.minecraft.client.audio.PositionedSoundRecord; import net.minecraft.init.Items; import net.minecraft.init.SoundEvents; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; +import java.util.List; + /** * Skin uploading GUI. Usually displayed over the main menu. */ @@ -28,15 +30,18 @@ public class GuiSkinsMineLP extends GuiSkins { private boolean isWet = false; - private static final String[] panoramas = new String[] { "minelp:textures/cubemap/sugarcubecorner_%d.png", "minelp:textures/cubemap/quillsandsofas_%d.png" }; + public GuiSkinsMineLP(List servers) { + super(servers); + } + @Override protected EntityPlayerModel getModel(GameProfile profile) { - return new EntityPonyModel(profile); + return new EntityPonyModel(this, profile); } @Override